Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
0bbfc743
Commit
0bbfc743
authored
Apr 18, 2018
by
BaiJiangjie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Feature] 支持otp
parent
7fc2ef00
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
938 additions
and
88 deletions
+938
-88
otp.css
apps/static/css/otp.css
+147
-0
iconfont.css
apps/static/fonts/font_otp/iconfont.css
+25
-0
iconfont.eot
apps/static/fonts/font_otp/iconfont.eot
+0
-0
iconfont.js
apps/static/fonts/font_otp/iconfont.js
+2
-0
iconfont.svg
apps/static/fonts/font_otp/iconfont.svg
+45
-0
iconfont.ttf
apps/static/fonts/font_otp/iconfont.ttf
+0
-0
iconfont.woff
apps/static/fonts/font_otp/iconfont.woff
+0
-0
authenticator_android.png
apps/static/img/authenticator_android.png
+0
-0
authenticator_iphone.png
apps/static/img/authenticator_iphone.png
+0
-0
otp_auth.png
apps/static/img/otp_auth.png
+0
-0
qrcode.min.js
apps/static/js/plugins/qrcode/qrcode.min.js
+2
-0
api.py
apps/users/api.py
+51
-23
forms.py
apps/users/forms.py
+12
-0
user.py
apps/users/models/user.py
+12
-6
serializers.py
apps/users/serializers.py
+4
-1
_base_otp.html
apps/users/templates/users/_base_otp.html
+87
-0
login_otp.html
apps/users/templates/users/login_otp.html
+87
-0
user_detail.html
apps/users/templates/users/user_detail.html
+50
-32
user_otp_authentication.html
apps/users/templates/users/user_otp_authentication.html
+37
-0
user_otp_enable_bind.html
apps/users/templates/users/user_otp_enable_bind.html
+54
-0
user_otp_enable_install_app.html
apps/users/templates/users/user_otp_enable_install_app.html
+32
-0
user_password_authentication.html
apps/users/templates/users/user_password_authentication.html
+25
-0
user_profile.html
apps/users/templates/users/user_profile.html
+31
-1
views_urls.py
apps/users/urls/views_urls.py
+6
-1
utils.py
apps/users/utils.py
+45
-2
login.py
apps/users/views/login.py
+55
-20
user.py
apps/users/views/user.py
+129
-2
No files found.
apps/static/css/otp.css
0 → 100644
View file @
0bbfc743
/*公共样式*/
*
{
margin
:
0
;
padding
:
0
;
outline
:
none
;
}
a
{
text-decoration
:
none
;
color
:
black
}
li
{
list-style
:
none
;
}
button
{
outline
:
none
;
}
.red-fonts
{
color
:
#ed5565
;
font-size
:
15px
;
text-align
:
center
;
}
/*header样式*/
header
{
overflow
:
hidden
;
background
:
#dedede
;
padding
:
15px
200px
;
}
header
.logo
a
{
float
:
left
;
}
header
.logo
a
:nth-child
(
2
)
{
padding-top
:
13px
;
}
header
div
:nth-child
(
1
)
{
float
:
left
;
}
header
div
:nth-child
(
2
)
{
float
:
right
;
font-size
:
12px
;
padding-top
:
20px
;
}
header
div
:nth-child
(
2
)
a
:hover
{
color
:
#1ab394
;
}
/*article样式*/
article
{
padding-top
:
50px
;
padding
:
50px
370px
}
article
ul
{
float
:
left
;
position
:
relative
;
left
:
50%
;
margin-bottom
:
50px
;
}
article
ul
li
{
float
:
left
;
position
:
relative
;
right
:
50%
;
}
article
ul
li
span
,
article
ul
li
i
{
display
:
block
;
float
:
left
;
}
article
ul
li
span
{
width
:
150px
;
height
:
4px
;
margin
:
15px
0
;
background
:
black
;
}
article
ul
li
:last-child
{
padding-left
:
2px
;
}
.iconfont
{
font-size
:
30px
;
color
:
grey
;
}
.back
{
margin-left
:
-15px
;
}
.active
{
color
:
#1ab394
;
}
.clearfix
:after
{
content
:
""
;
height
:
0
;
visibility
:
hidden
;
display
:
block
;
clear
:
both
;
}
.verify
{
text-align
:
center
;
font-size
:
14px
;
/*padding-left:70px;*/
color
:
grey
;
}
.verify
span
{
color
:
red
;
}
.line
{
width
:
500px
;
height
:
1px
;
margin-left
:
100px
;
margin-top
:
10px
;
background
:
grey
;
}
/*输入框样式*/
.form-input
{
text-align
:
center
;
margin
:
20px
auto
;
}
.form-input
input
{
width
:
200px
;
height
:
30px
;
padding-left
:
10px
;
outline
:
none
;
}
/*身份验证*/
/*安装应用*/
.verify
div
{
display
:
inline-block
;
}
.verify
div
:nth-child
(
3
)
{
margin-left
:
58px
;
}
.next
{
margin
:
20px
auto
;
display
:
block
;
width
:
214px
;
line-height
:
34px
;
background
:
#1ab394
;
text-align
:
center
;
border-radius
:
6px
;
color
:
white
;
}
/*绑定TOTP*/
/*版权信息*/
footer
{
text-align
:
center
;
font-size
:
14px
;
color
:
#1a1a1a
;
}
\ No newline at end of file
apps/static/fonts/font_otp/iconfont.css
0 → 100644
View file @
0bbfc743
@font-face
{
font-family
:
"iconfont"
;
src
:
url('iconfont.eot?t=1523776860888')
;
/* IE9*/
src
:
url('iconfont.eot?t=1523776860888#iefix')
format
(
'embedded-opentype'
),
/* IE6-IE8 */
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAY4AAsAAAAACVwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kggY21hcAAAAYAAAAB0AAABuM8DAsdnbHlmAAAB9AAAAjgAAALsJ9wRv2hlYWQAAAQsAAAALwAAADYREYC1aGhlYQAABFwAAAAcAAAAJAfeA4dobXR4AAAEeAAAABMAAAAYF+kAAGxvY2EAAASMAAAADgAAAA4C0gHmbWF4cAAABJwAAAAfAAAAIAEVAF1uYW1lAAAEvAAAAUUAAAJtPlT+fXBvc3QAAAYEAAAANAAAAEtj7FVFeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sM4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDwzYm7438AQw9zA0AAUZgTJAQAoXgyieJzFkc0NgCAMhV/5McYQZBBPHJ3BOTw5ABN3DWwLFyfwka+0LyUQCiAC8MIhBIAeEFS3uGS+x2Z+wCn1KsvJ3rhw7d2yPDMVWUeyzMuZqN204DfRf1d/lSxes9J/bxN5IueBzoL3gc6Dy0D7uQ7gXrxnFIt4nG2Su2/TUBSH77mO7aTNg1zHdt6JbWq3hIbUj7giipNWhRKoAm0BlaWIh5goDCBFSAxZqiBgqNhZUAVTBTuVWlgZO4CyRAj+jd7ipguVcnW330/6vqNzEIvQ0W9ml0kiAU2iGbSAbiAEXAnUKM6BYthlXAJRYUU5EWUMzVB4TS0zdZBVLiGZVVuXOZ6LQRTyYClm1ShjAxzbwzUwpRxAKpNeJRNZwmzBWNLIb9Kr+AOIBS0b86Zp63wjYRaFYCdMSIqQt0GOZYMYB2JR2JClEBsa4+g2G0uLu4UpXIBwykgv3YkUM+TeK/tJbkIOAXS7IGSK0U+NeDru/5dpSSAp/kwkmExHtLMJ6PwdTwrhnP4H+Q/7s3YDiOmicZQ/nhLxEpKryNWRAJx6AcrgVqUCgAeyxGHUpwOWBaXfB4Vl6eAR3RFs4YAhYkqHfWiCnhIJ0/WT/n9Nukl3CDk4Cf3WsH7C/sp8Z+YQQVmfremaEgU+zol5kD1wy8AojhXnHTduwRFgoN+cRcAs3dungQDN04+9Nz97TANg0aEDIuxRdpgdVubhx+TlxuGv0wx7NIPTVMORLLPq2CVwLNOxNZV3qqYkJni/JSZGsB/W7t+2vbbX2rYq7542Z2vv11/MLY1QWTOL1yt1+1nj8YNSS11udlTMt2srp7zOjfYSFcf1/MPRhzrWsY9/VeIImym69aU1c9Fdbl1ZX7lbv/l6hMjzhfnPrVvTtnup7a5dm91Y7YG//n+HyatVeJxjYGRgYADiTatcAuP5bb4ycLMwgMC1n3IxCPp/AwsDcwOQy8HABBIFACp7CkQAeJxjYGRgYG7438AQw8IAAkCSkQEVsAEARwwCb3icY2FgYGB+ycDAwoCKARKfAQEAAAAAAAB2ALQA5gEyAXYAAHicY2BkYGBgYwhkYGUAASYg5gJCBob/YD4DABFIAXMAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicY2BigAAuBuyAjZGJkZmRhZGVkY2RnYGxgi2lNDM9v5SluCS1gBVEGIJJIwYGAIzACOU=')
format
(
'woff'
),
url('iconfont.ttf?t=1523776860888')
format
(
'truetype'
),
/* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('iconfont.svg?t=1523776860888#iconfont')
format
(
'svg'
);
/* iOS 4.1- */
}
.iconfont
{
font-family
:
"iconfont"
!important
;
font-size
:
16px
;
font-style
:
normal
;
-webkit-font-smoothing
:
antialiased
;
-moz-osx-font-smoothing
:
grayscale
;
}
.icon-duigou
:before
{
content
:
"\e632"
;
}
.icon-step
:before
{
content
:
"\e60e"
;
}
.icon-step1
:before
{
content
:
"\e60f"
;
}
.icon-step2
:before
{
content
:
"\e610"
;
}
apps/static/fonts/font_otp/iconfont.eot
0 → 100644
View file @
0bbfc743
File added
apps/static/fonts/font_otp/iconfont.js
0 → 100644
View file @
0bbfc743
(
function
(
window
){
var
svgSprite
=
'<svg><symbol id="icon-duigou" viewBox="0 0 1024 1024"><path d="M512 0C228.266667 0 0 228.266667 0 512c0 283.733333 228.266667 512 512 512 283.733333 0 512-228.266667 512-512C1024 228.266667 795.733333 0 512 0zM832 384 492.8 723.2C469.333333 746.666667 426.666667 746.666667 403.2 723.2L192 512c0 0-32-32 0-64s64 0 64 0l192 192 320-320c0 0 32-32 64 0S832 384 832 384z" ></path></symbol><symbol id="icon-step" viewBox="0 0 1024 1024"><path d="M511.3 64.6h-1.8c-0.7-0.4-1.6-0.7-2.5-0.7H188.3c-69 0-125.5 56.5-125.5 125.5v289.1c-0.9 11.9-1.4 24-1.4 36.1 0 248.5 201.5 450 450 450s450-201.5 450-450-201.5-450-450.1-450z m162.1 724.8H326.5v-66H462V264l-139 40.2v-70.3l215.2-62.5v552h135.2v66z" ></path></symbol><symbol id="icon-step1" viewBox="0 0 1024 1024"><path d="M511.3 64.6h-1.8c-0.7-0.4-1.6-0.7-2.5-0.7H188.3c-69 0-125.5 56.5-125.5 125.5v289.1c-0.9 11.9-1.4 24-1.4 36.1 0 248.5 201.5 450 450 450s450-201.5 450-450-201.5-450-450.1-450z m91.4 684.9c-39.2 33.3-91.3 50-156.4 50-57.3 0-103.5-10.7-138.7-32v-79.3c41.4 32 88.1 48 140.2 48 41.7 0 74.7-10.2 99-30.7 24.3-20.4 36.5-47.9 36.5-82.2 0-76.6-54.8-114.8-164.5-114.8h-50.4v-63.3h48c97.1 0 145.7-35.9 145.7-107.8 0-66.4-37.1-99.6-111.3-99.6-42.5 0-82.4 14.3-119.9 43v-72.3c39.6-22.9 85.8-34.4 138.7-34.4 51.6 0 93 13.5 124.2 40.4s46.9 61.9 46.9 104.9c0 79.2-40.4 130.1-121.1 152.7v1.6c43.8 4.7 78.3 20.1 103.7 46.3s38.1 58.8 38.1 97.9c0 54.4-19.6 98.3-58.7 131.6z" ></path></symbol><symbol id="icon-step2" viewBox="0 0 1024 1024"><path d="M511.3 64.6h-1.8c-0.7-0.4-1.6-0.7-2.5-0.7H188.3c-69 0-125.5 56.5-125.5 125.5v289.1c-0.9 11.9-1.4 24-1.4 36.1 0 248.5 201.5 450 450 450s450-201.5 450-450-201.5-450-450.1-450z m150.8 656.8v68h-368V723l175.8-175.4c48.4-48.4 80.9-86.8 97.3-115 16.4-28.3 24.6-57.5 24.6-87.7 0-34.4-9.6-60.7-28.9-79.1-19.3-18.4-47.1-27.5-83.6-27.5-53.9 0-105.3 22.9-154.3 68.8v-77.7c47.7-36.7 103.1-55.1 166.4-55.1 54.4 0 97.4 14.7 128.9 44.1 31.5 29.4 47.3 69 47.3 118.8 0 37.5-10.1 74.3-30.3 110.4-20.2 36.1-58.4 82-114.6 137.7l-138 134.5v1.6h277.4z" ></path></symbol></svg>'
;
var
script
=
function
(){
var
scripts
=
document
.
getElementsByTagName
(
"script"
);
return
scripts
[
scripts
.
length
-
1
]}();
var
shouldInjectCss
=
script
.
getAttribute
(
"data-injectcss"
);
var
ready
=
function
(
fn
){
if
(
document
.
addEventListener
){
if
(
~
[
"complete"
,
"loaded"
,
"interactive"
].
indexOf
(
document
.
readyState
)){
setTimeout
(
fn
,
0
)}
else
{
var
loadFn
=
function
(){
document
.
removeEventListener
(
"DOMContentLoaded"
,
loadFn
,
false
);
fn
()};
document
.
addEventListener
(
"DOMContentLoaded"
,
loadFn
,
false
)}}
else
if
(
document
.
attachEvent
){
IEContentLoaded
(
window
,
fn
)}
function
IEContentLoaded
(
w
,
fn
){
var
d
=
w
.
document
,
done
=
false
,
init
=
function
(){
if
(
!
done
){
done
=
true
;
fn
()}};
var
polling
=
function
(){
try
{
d
.
documentElement
.
doScroll
(
"left"
)}
catch
(
e
){
setTimeout
(
polling
,
50
);
return
}
init
()};
polling
();
d
.
onreadystatechange
=
function
(){
if
(
d
.
readyState
==
"complete"
){
d
.
onreadystatechange
=
null
;
init
()}}}};
var
before
=
function
(
el
,
target
){
target
.
parentNode
.
insertBefore
(
el
,
target
)};
var
prepend
=
function
(
el
,
target
){
if
(
target
.
firstChild
){
before
(
el
,
target
.
firstChild
)}
else
{
target
.
appendChild
(
el
)}};
function
appendSvg
(){
var
div
,
svg
;
div
=
document
.
createElement
(
"div"
);
div
.
innerHTML
=
svgSprite
;
svgSprite
=
null
;
svg
=
div
.
getElementsByTagName
(
"svg"
)[
0
];
if
(
svg
){
svg
.
setAttribute
(
"aria-hidden"
,
"true"
);
svg
.
style
.
position
=
"absolute"
;
svg
.
style
.
width
=
0
;
svg
.
style
.
height
=
0
;
svg
.
style
.
overflow
=
"hidden"
;
prepend
(
svg
,
document
.
body
)}}
if
(
shouldInjectCss
&&!
window
.
__iconfont__svg__cssinject__
){
window
.
__iconfont__svg__cssinject__
=
true
;
try
{
document
.
write
(
"<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>"
)}
catch
(
e
){
console
&&
console
.
log
(
e
)}}
ready
(
appendSvg
)})(
window
)
\ No newline at end of file
apps/static/fonts/font_otp/iconfont.svg
0 → 100644
View file @
0bbfc743
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font
id=
"iconfont"
horiz-adv-x=
"1024"
>
<font-face
font-family=
"iconfont"
font-weight=
"500"
font-stretch=
"normal"
units-per-em=
"1024"
ascent=
"896"
descent=
"-128"
/>
<missing-glyph
/>
<glyph
glyph-name=
"x"
unicode=
"x"
horiz-adv-x=
"1001"
d=
"M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z"
/>
<glyph
glyph-name=
"duigou"
unicode=
""
d=
"M512 896C228.266667 896 0 667.733333 0 384c0-283.733333 228.266667-512 512-512 283.733333 0 512 228.266667 512 512C1024 667.733333 795.733333 896 512 896zM832 512 492.8 172.8C469.333333 149.333333 426.666667 149.333333 403.2 172.8L192 384c0 0-32 32 0 64s64 0 64 0l192-192 320 320c0 0 32 32 64 0S832 512 832 512z"
horiz-adv-x=
"1024"
/>
<glyph
glyph-name=
"step"
unicode=
""
d=
"M511.3 831.4h-1.8c-0.7 0.4-1.6 0.7-2.5 0.7H188.3c-69 0-125.5-56.5-125.5-125.5v-289.1c-0.9-11.9-1.4-24-1.4-36.1 0-248.5 201.5-450 450-450s450 201.5 450 450-201.5 450-450.1 450z m162.1-724.8H326.5v66H462V632l-139-40.2v70.3l215.2 62.5v-552h135.2v-66z"
horiz-adv-x=
"1024"
/>
<glyph
glyph-name=
"step1"
unicode=
""
d=
"M511.3 831.4h-1.8c-0.7 0.4-1.6 0.7-2.5 0.7H188.3c-69 0-125.5-56.5-125.5-125.5v-289.1c-0.9-11.9-1.4-24-1.4-36.1 0-248.5 201.5-450 450-450s450 201.5 450 450-201.5 450-450.1 450z m91.4-684.9c-39.2-33.3-91.3-50-156.4-50-57.3 0-103.5 10.7-138.7 32v79.3c41.4-32 88.1-48 140.2-48 41.7 0 74.7 10.2 99 30.7 24.3 20.4 36.5 47.9 36.5 82.2 0 76.6-54.8 114.8-164.5 114.8h-50.4v63.3h48c97.1 0 145.7 35.9 145.7 107.8 0 66.4-37.1 99.6-111.3 99.6-42.5 0-82.4-14.3-119.9-43v72.3c39.6 22.9 85.8 34.4 138.7 34.4 51.6 0 93-13.5 124.2-40.4s46.9-61.9 46.9-104.9c0-79.2-40.4-130.1-121.1-152.7v-1.6c43.8-4.7 78.3-20.1 103.7-46.3s38.1-58.8 38.1-97.9c0-54.4-19.6-98.3-58.7-131.6z"
horiz-adv-x=
"1024"
/>
<glyph
glyph-name=
"step2"
unicode=
""
d=
"M511.3 831.4h-1.8c-0.7 0.4-1.6 0.7-2.5 0.7H188.3c-69 0-125.5-56.5-125.5-125.5v-289.1c-0.9-11.9-1.4-24-1.4-36.1 0-248.5 201.5-450 450-450s450 201.5 450 450-201.5 450-450.1 450z m150.8-656.8v-68h-368V173l175.8 175.4c48.4 48.4 80.9 86.8 97.3 115 16.4 28.3 24.6 57.5 24.6 87.7 0 34.4-9.6 60.7-28.9 79.1-19.3 18.4-47.1 27.5-83.6 27.5-53.9 0-105.3-22.9-154.3-68.8v77.7c47.7 36.7 103.1 55.1 166.4 55.1 54.4 0 97.4-14.7 128.9-44.1 31.5-29.4 47.3-69 47.3-118.8 0-37.5-10.1-74.3-30.3-110.4-20.2-36.1-58.4-82-114.6-137.7l-138-134.5v-1.6h277.4z"
horiz-adv-x=
"1024"
/>
</font>
</defs></svg>
apps/static/fonts/font_otp/iconfont.ttf
0 → 100644
View file @
0bbfc743
File added
apps/static/fonts/font_otp/iconfont.woff
0 → 100644
View file @
0bbfc743
File added
apps/static/img/authenticator_android.png
0 → 100644
View file @
0bbfc743
2.39 KB
apps/static/img/authenticator_iphone.png
0 → 100644
View file @
0bbfc743
187 KB
apps/static/img/otp_auth.png
0 → 100644
View file @
0bbfc743
3.41 KB
apps/static/js/plugins/qrcode/qrcode.min.js
0 → 100755
View file @
0bbfc743
var
QRCode
;
!
function
(){
function
a
(
a
){
this
.
mode
=
c
.
MODE_8BIT_BYTE
,
this
.
data
=
a
,
this
.
parsedData
=
[];
for
(
var
b
=
[],
d
=
0
,
e
=
this
.
data
.
length
;
e
>
d
;
d
++
){
var
f
=
this
.
data
.
charCodeAt
(
d
);
f
>
65536
?(
b
[
0
]
=
240
|
(
1835008
&
f
)
>>>
18
,
b
[
1
]
=
128
|
(
258048
&
f
)
>>>
12
,
b
[
2
]
=
128
|
(
4032
&
f
)
>>>
6
,
b
[
3
]
=
128
|
63
&
f
):
f
>
2048
?(
b
[
0
]
=
224
|
(
61440
&
f
)
>>>
12
,
b
[
1
]
=
128
|
(
4032
&
f
)
>>>
6
,
b
[
2
]
=
128
|
63
&
f
):
f
>
128
?(
b
[
0
]
=
192
|
(
1984
&
f
)
>>>
6
,
b
[
1
]
=
128
|
63
&
f
):
b
[
0
]
=
f
,
this
.
parsedData
=
this
.
parsedData
.
concat
(
b
)}
this
.
parsedData
.
length
!=
this
.
data
.
length
&&
(
this
.
parsedData
.
unshift
(
191
),
this
.
parsedData
.
unshift
(
187
),
this
.
parsedData
.
unshift
(
239
))}
function
b
(
a
,
b
){
this
.
typeNumber
=
a
,
this
.
errorCorrectLevel
=
b
,
this
.
modules
=
null
,
this
.
moduleCount
=
0
,
this
.
dataCache
=
null
,
this
.
dataList
=
[]}
function
i
(
a
,
b
){
if
(
void
0
==
a
.
length
)
throw
new
Error
(
a
.
length
+
"/"
+
b
);
for
(
var
c
=
0
;
c
<
a
.
length
&&
0
==
a
[
c
];)
c
++
;
this
.
num
=
new
Array
(
a
.
length
-
c
+
b
);
for
(
var
d
=
0
;
d
<
a
.
length
-
c
;
d
++
)
this
.
num
[
d
]
=
a
[
d
+
c
]}
function
j
(
a
,
b
){
this
.
totalCount
=
a
,
this
.
dataCount
=
b
}
function
k
(){
this
.
buffer
=
[],
this
.
length
=
0
}
function
m
(){
return
"undefined"
!=
typeof
CanvasRenderingContext2D
}
function
n
(){
var
a
=!
1
,
b
=
navigator
.
userAgent
;
return
/android/i
.
test
(
b
)
&&
(
a
=!
0
,
aMat
=
b
.
toString
().
match
(
/android
([
0-9
]\.[
0-9
])
/i
),
aMat
&&
aMat
[
1
]
&&
(
a
=
parseFloat
(
aMat
[
1
]))),
a
}
function
r
(
a
,
b
){
for
(
var
c
=
1
,
e
=
s
(
a
),
f
=
0
,
g
=
l
.
length
;
g
>=
f
;
f
++
){
var
h
=
0
;
switch
(
b
){
case
d
.
L
:
h
=
l
[
f
][
0
];
break
;
case
d
.
M
:
h
=
l
[
f
][
1
];
break
;
case
d
.
Q
:
h
=
l
[
f
][
2
];
break
;
case
d
.
H
:
h
=
l
[
f
][
3
]}
if
(
h
>=
e
)
break
;
c
++
}
if
(
c
>
l
.
length
)
throw
new
Error
(
"Too long data"
);
return
c
}
function
s
(
a
){
var
b
=
encodeURI
(
a
).
toString
().
replace
(
/
\%[
0-9a-fA-F
]{2}
/g
,
"a"
);
return
b
.
length
+
(
b
.
length
!=
a
?
3
:
0
)}
a
.
prototype
=
{
getLength
:
function
(){
return
this
.
parsedData
.
length
},
write
:
function
(
a
){
for
(
var
b
=
0
,
c
=
this
.
parsedData
.
length
;
c
>
b
;
b
++
)
a
.
put
(
this
.
parsedData
[
b
],
8
)}},
b
.
prototype
=
{
addData
:
function
(
b
){
var
c
=
new
a
(
b
);
this
.
dataList
.
push
(
c
),
this
.
dataCache
=
null
},
isDark
:
function
(
a
,
b
){
if
(
0
>
a
||
this
.
moduleCount
<=
a
||
0
>
b
||
this
.
moduleCount
<=
b
)
throw
new
Error
(
a
+
","
+
b
);
return
this
.
modules
[
a
][
b
]},
getModuleCount
:
function
(){
return
this
.
moduleCount
},
make
:
function
(){
this
.
makeImpl
(
!
1
,
this
.
getBestMaskPattern
())},
makeImpl
:
function
(
a
,
c
){
this
.
moduleCount
=
4
*
this
.
typeNumber
+
17
,
this
.
modules
=
new
Array
(
this
.
moduleCount
);
for
(
var
d
=
0
;
d
<
this
.
moduleCount
;
d
++
){
this
.
modules
[
d
]
=
new
Array
(
this
.
moduleCount
);
for
(
var
e
=
0
;
e
<
this
.
moduleCount
;
e
++
)
this
.
modules
[
d
][
e
]
=
null
}
this
.
setupPositionProbePattern
(
0
,
0
),
this
.
setupPositionProbePattern
(
this
.
moduleCount
-
7
,
0
),
this
.
setupPositionProbePattern
(
0
,
this
.
moduleCount
-
7
),
this
.
setupPositionAdjustPattern
(),
this
.
setupTimingPattern
(),
this
.
setupTypeInfo
(
a
,
c
),
this
.
typeNumber
>=
7
&&
this
.
setupTypeNumber
(
a
),
null
==
this
.
dataCache
&&
(
this
.
dataCache
=
b
.
createData
(
this
.
typeNumber
,
this
.
errorCorrectLevel
,
this
.
dataList
)),
this
.
mapData
(
this
.
dataCache
,
c
)},
setupPositionProbePattern
:
function
(
a
,
b
){
for
(
var
c
=-
1
;
7
>=
c
;
c
++
)
if
(
!
(
-
1
>=
a
+
c
||
this
.
moduleCount
<=
a
+
c
))
for
(
var
d
=-
1
;
7
>=
d
;
d
++
)
-
1
>=
b
+
d
||
this
.
moduleCount
<=
b
+
d
||
(
this
.
modules
[
a
+
c
][
b
+
d
]
=
c
>=
0
&&
6
>=
c
&&
(
0
==
d
||
6
==
d
)
||
d
>=
0
&&
6
>=
d
&&
(
0
==
c
||
6
==
c
)
||
c
>=
2
&&
4
>=
c
&&
d
>=
2
&&
4
>=
d
?
!
0
:
!
1
)},
getBestMaskPattern
:
function
(){
for
(
var
a
=
0
,
b
=
0
,
c
=
0
;
8
>
c
;
c
++
){
this
.
makeImpl
(
!
0
,
c
);
var
d
=
f
.
getLostPoint
(
this
);(
0
==
c
||
a
>
d
)
&&
(
a
=
d
,
b
=
c
)}
return
b
},
createMovieClip
:
function
(
a
,
b
,
c
){
var
d
=
a
.
createEmptyMovieClip
(
b
,
c
),
e
=
1
;
this
.
make
();
for
(
var
f
=
0
;
f
<
this
.
modules
.
length
;
f
++
)
for
(
var
g
=
f
*
e
,
h
=
0
;
h
<
this
.
modules
[
f
].
length
;
h
++
){
var
i
=
h
*
e
,
j
=
this
.
modules
[
f
][
h
];
j
&&
(
d
.
beginFill
(
0
,
100
),
d
.
moveTo
(
i
,
g
),
d
.
lineTo
(
i
+
e
,
g
),
d
.
lineTo
(
i
+
e
,
g
+
e
),
d
.
lineTo
(
i
,
g
+
e
),
d
.
endFill
())}
return
d
},
setupTimingPattern
:
function
(){
for
(
var
a
=
8
;
a
<
this
.
moduleCount
-
8
;
a
++
)
null
==
this
.
modules
[
a
][
6
]
&&
(
this
.
modules
[
a
][
6
]
=
0
==
a
%
2
);
for
(
var
b
=
8
;
b
<
this
.
moduleCount
-
8
;
b
++
)
null
==
this
.
modules
[
6
][
b
]
&&
(
this
.
modules
[
6
][
b
]
=
0
==
b
%
2
)},
setupPositionAdjustPattern
:
function
(){
for
(
var
a
=
f
.
getPatternPosition
(
this
.
typeNumber
),
b
=
0
;
b
<
a
.
length
;
b
++
)
for
(
var
c
=
0
;
c
<
a
.
length
;
c
++
){
var
d
=
a
[
b
],
e
=
a
[
c
];
if
(
null
==
this
.
modules
[
d
][
e
])
for
(
var
g
=-
2
;
2
>=
g
;
g
++
)
for
(
var
h
=-
2
;
2
>=
h
;
h
++
)
this
.
modules
[
d
+
g
][
e
+
h
]
=-
2
==
g
||
2
==
g
||-
2
==
h
||
2
==
h
||
0
==
g
&&
0
==
h
?
!
0
:
!
1
}},
setupTypeNumber
:
function
(
a
){
for
(
var
b
=
f
.
getBCHTypeNumber
(
this
.
typeNumber
),
c
=
0
;
18
>
c
;
c
++
){
var
d
=!
a
&&
1
==
(
1
&
b
>>
c
);
this
.
modules
[
Math
.
floor
(
c
/
3
)][
c
%
3
+
this
.
moduleCount
-
8
-
3
]
=
d
}
for
(
var
c
=
0
;
18
>
c
;
c
++
){
var
d
=!
a
&&
1
==
(
1
&
b
>>
c
);
this
.
modules
[
c
%
3
+
this
.
moduleCount
-
8
-
3
][
Math
.
floor
(
c
/
3
)]
=
d
}},
setupTypeInfo
:
function
(
a
,
b
){
for
(
var
c
=
this
.
errorCorrectLevel
<<
3
|
b
,
d
=
f
.
getBCHTypeInfo
(
c
),
e
=
0
;
15
>
e
;
e
++
){
var
g
=!
a
&&
1
==
(
1
&
d
>>
e
);
6
>
e
?
this
.
modules
[
e
][
8
]
=
g
:
8
>
e
?
this
.
modules
[
e
+
1
][
8
]
=
g
:
this
.
modules
[
this
.
moduleCount
-
15
+
e
][
8
]
=
g
}
for
(
var
e
=
0
;
15
>
e
;
e
++
){
var
g
=!
a
&&
1
==
(
1
&
d
>>
e
);
8
>
e
?
this
.
modules
[
8
][
this
.
moduleCount
-
e
-
1
]
=
g
:
9
>
e
?
this
.
modules
[
8
][
15
-
e
-
1
+
1
]
=
g
:
this
.
modules
[
8
][
15
-
e
-
1
]
=
g
}
this
.
modules
[
this
.
moduleCount
-
8
][
8
]
=!
a
},
mapData
:
function
(
a
,
b
){
for
(
var
c
=-
1
,
d
=
this
.
moduleCount
-
1
,
e
=
7
,
g
=
0
,
h
=
this
.
moduleCount
-
1
;
h
>
0
;
h
-=
2
)
for
(
6
==
h
&&
h
--
;;){
for
(
var
i
=
0
;
2
>
i
;
i
++
)
if
(
null
==
this
.
modules
[
d
][
h
-
i
]){
var
j
=!
1
;
g
<
a
.
length
&&
(
j
=
1
==
(
1
&
a
[
g
]
>>>
e
));
var
k
=
f
.
getMask
(
b
,
d
,
h
-
i
);
k
&&
(
j
=!
j
),
this
.
modules
[
d
][
h
-
i
]
=
j
,
e
--
,
-
1
==
e
&&
(
g
++
,
e
=
7
)}
if
(
d
+=
c
,
0
>
d
||
this
.
moduleCount
<=
d
){
d
-=
c
,
c
=-
c
;
break
}}}},
b
.
PAD0
=
236
,
b
.
PAD1
=
17
,
b
.
createData
=
function
(
a
,
c
,
d
){
for
(
var
e
=
j
.
getRSBlocks
(
a
,
c
),
g
=
new
k
,
h
=
0
;
h
<
d
.
length
;
h
++
){
var
i
=
d
[
h
];
g
.
put
(
i
.
mode
,
4
),
g
.
put
(
i
.
getLength
(),
f
.
getLengthInBits
(
i
.
mode
,
a
)),
i
.
write
(
g
)}
for
(
var
l
=
0
,
h
=
0
;
h
<
e
.
length
;
h
++
)
l
+=
e
[
h
].
dataCount
;
if
(
g
.
getLengthInBits
()
>
8
*
l
)
throw
new
Error
(
"code length overflow. ("
+
g
.
getLengthInBits
()
+
">"
+
8
*
l
+
")"
);
for
(
g
.
getLengthInBits
()
+
4
<=
8
*
l
&&
g
.
put
(
0
,
4
);
0
!=
g
.
getLengthInBits
()
%
8
;)
g
.
putBit
(
!
1
);
for
(;;){
if
(
g
.
getLengthInBits
()
>=
8
*
l
)
break
;
if
(
g
.
put
(
b
.
PAD0
,
8
),
g
.
getLengthInBits
()
>=
8
*
l
)
break
;
g
.
put
(
b
.
PAD1
,
8
)}
return
b
.
createBytes
(
g
,
e
)},
b
.
createBytes
=
function
(
a
,
b
){
for
(
var
c
=
0
,
d
=
0
,
e
=
0
,
g
=
new
Array
(
b
.
length
),
h
=
new
Array
(
b
.
length
),
j
=
0
;
j
<
b
.
length
;
j
++
){
var
k
=
b
[
j
].
dataCount
,
l
=
b
[
j
].
totalCount
-
k
;
d
=
Math
.
max
(
d
,
k
),
e
=
Math
.
max
(
e
,
l
),
g
[
j
]
=
new
Array
(
k
);
for
(
var
m
=
0
;
m
<
g
[
j
].
length
;
m
++
)
g
[
j
][
m
]
=
255
&
a
.
buffer
[
m
+
c
];
c
+=
k
;
var
n
=
f
.
getErrorCorrectPolynomial
(
l
),
o
=
new
i
(
g
[
j
],
n
.
getLength
()
-
1
),
p
=
o
.
mod
(
n
);
h
[
j
]
=
new
Array
(
n
.
getLength
()
-
1
);
for
(
var
m
=
0
;
m
<
h
[
j
].
length
;
m
++
){
var
q
=
m
+
p
.
getLength
()
-
h
[
j
].
length
;
h
[
j
][
m
]
=
q
>=
0
?
p
.
get
(
q
):
0
}}
for
(
var
r
=
0
,
m
=
0
;
m
<
b
.
length
;
m
++
)
r
+=
b
[
m
].
totalCount
;
for
(
var
s
=
new
Array
(
r
),
t
=
0
,
m
=
0
;
d
>
m
;
m
++
)
for
(
var
j
=
0
;
j
<
b
.
length
;
j
++
)
m
<
g
[
j
].
length
&&
(
s
[
t
++
]
=
g
[
j
][
m
]);
for
(
var
m
=
0
;
e
>
m
;
m
++
)
for
(
var
j
=
0
;
j
<
b
.
length
;
j
++
)
m
<
h
[
j
].
length
&&
(
s
[
t
++
]
=
h
[
j
][
m
]);
return
s
};
for
(
var
c
=
{
MODE_NUMBER
:
1
,
MODE_ALPHA_NUM
:
2
,
MODE_8BIT_BYTE
:
4
,
MODE_KANJI
:
8
},
d
=
{
L
:
1
,
M
:
0
,
Q
:
3
,
H
:
2
},
e
=
{
PATTERN000
:
0
,
PATTERN001
:
1
,
PATTERN010
:
2
,
PATTERN011
:
3
,
PATTERN100
:
4
,
PATTERN101
:
5
,
PATTERN110
:
6
,
PATTERN111
:
7
},
f
=
{
PATTERN_POSITION_TABLE
:[[],[
6
,
18
],[
6
,
22
],[
6
,
26
],[
6
,
30
],[
6
,
34
],[
6
,
22
,
38
],[
6
,
24
,
42
],[
6
,
26
,
46
],[
6
,
28
,
50
],[
6
,
30
,
54
],[
6
,
32
,
58
],[
6
,
34
,
62
],[
6
,
26
,
46
,
66
],[
6
,
26
,
48
,
70
],[
6
,
26
,
50
,
74
],[
6
,
30
,
54
,
78
],[
6
,
30
,
56
,
82
],[
6
,
30
,
58
,
86
],[
6
,
34
,
62
,
90
],[
6
,
28
,
50
,
72
,
94
],[
6
,
26
,
50
,
74
,
98
],[
6
,
30
,
54
,
78
,
102
],[
6
,
28
,
54
,
80
,
106
],[
6
,
32
,
58
,
84
,
110
],[
6
,
30
,
58
,
86
,
114
],[
6
,
34
,
62
,
90
,
118
],[
6
,
26
,
50
,
74
,
98
,
122
],[
6
,
30
,
54
,
78
,
102
,
126
],[
6
,
26
,
52
,
78
,
104
,
130
],[
6
,
30
,
56
,
82
,
108
,
134
],[
6
,
34
,
60
,
86
,
112
,
138
],[
6
,
30
,
58
,
86
,
114
,
142
],[
6
,
34
,
62
,
90
,
118
,
146
],[
6
,
30
,
54
,
78
,
102
,
126
,
150
],[
6
,
24
,
50
,
76
,
102
,
128
,
154
],[
6
,
28
,
54
,
80
,
106
,
132
,
158
],[
6
,
32
,
58
,
84
,
110
,
136
,
162
],[
6
,
26
,
54
,
82
,
110
,
138
,
166
],[
6
,
30
,
58
,
86
,
114
,
142
,
170
]],
G15
:
1335
,
G18
:
7973
,
G15_MASK
:
21522
,
getBCHTypeInfo
:
function
(
a
){
for
(
var
b
=
a
<<
10
;
f
.
getBCHDigit
(
b
)
-
f
.
getBCHDigit
(
f
.
G15
)
>=
0
;)
b
^=
f
.
G15
<<
f
.
getBCHDigit
(
b
)
-
f
.
getBCHDigit
(
f
.
G15
);
return
(
a
<<
10
|
b
)
^
f
.
G15_MASK
},
getBCHTypeNumber
:
function
(
a
){
for
(
var
b
=
a
<<
12
;
f
.
getBCHDigit
(
b
)
-
f
.
getBCHDigit
(
f
.
G18
)
>=
0
;)
b
^=
f
.
G18
<<
f
.
getBCHDigit
(
b
)
-
f
.
getBCHDigit
(
f
.
G18
);
return
a
<<
12
|
b
},
getBCHDigit
:
function
(
a
){
for
(
var
b
=
0
;
0
!=
a
;)
b
++
,
a
>>>=
1
;
return
b
},
getPatternPosition
:
function
(
a
){
return
f
.
PATTERN_POSITION_TABLE
[
a
-
1
]},
getMask
:
function
(
a
,
b
,
c
){
switch
(
a
){
case
e
.
PATTERN000
:
return
0
==
(
b
+
c
)
%
2
;
case
e
.
PATTERN001
:
return
0
==
b
%
2
;
case
e
.
PATTERN010
:
return
0
==
c
%
3
;
case
e
.
PATTERN011
:
return
0
==
(
b
+
c
)
%
3
;
case
e
.
PATTERN100
:
return
0
==
(
Math
.
floor
(
b
/
2
)
+
Math
.
floor
(
c
/
3
))
%
2
;
case
e
.
PATTERN101
:
return
0
==
b
*
c
%
2
+
b
*
c
%
3
;
case
e
.
PATTERN110
:
return
0
==
(
b
*
c
%
2
+
b
*
c
%
3
)
%
2
;
case
e
.
PATTERN111
:
return
0
==
(
b
*
c
%
3
+
(
b
+
c
)
%
2
)
%
2
;
default
:
throw
new
Error
(
"bad maskPattern:"
+
a
)}},
getErrorCorrectPolynomial
:
function
(
a
){
for
(
var
b
=
new
i
([
1
],
0
),
c
=
0
;
a
>
c
;
c
++
)
b
=
b
.
multiply
(
new
i
([
1
,
g
.
gexp
(
c
)],
0
));
return
b
},
getLengthInBits
:
function
(
a
,
b
){
if
(
b
>=
1
&&
10
>
b
)
switch
(
a
){
case
c
.
MODE_NUMBER
:
return
10
;
case
c
.
MODE_ALPHA_NUM
:
return
9
;
case
c
.
MODE_8BIT_BYTE
:
return
8
;
case
c
.
MODE_KANJI
:
return
8
;
default
:
throw
new
Error
(
"mode:"
+
a
)}
else
if
(
27
>
b
)
switch
(
a
){
case
c
.
MODE_NUMBER
:
return
12
;
case
c
.
MODE_ALPHA_NUM
:
return
11
;
case
c
.
MODE_8BIT_BYTE
:
return
16
;
case
c
.
MODE_KANJI
:
return
10
;
default
:
throw
new
Error
(
"mode:"
+
a
)}
else
{
if
(
!
(
41
>
b
))
throw
new
Error
(
"type:"
+
b
);
switch
(
a
){
case
c
.
MODE_NUMBER
:
return
14
;
case
c
.
MODE_ALPHA_NUM
:
return
13
;
case
c
.
MODE_8BIT_BYTE
:
return
16
;
case
c
.
MODE_KANJI
:
return
12
;
default
:
throw
new
Error
(
"mode:"
+
a
)}}},
getLostPoint
:
function
(
a
){
for
(
var
b
=
a
.
getModuleCount
(),
c
=
0
,
d
=
0
;
b
>
d
;
d
++
)
for
(
var
e
=
0
;
b
>
e
;
e
++
){
for
(
var
f
=
0
,
g
=
a
.
isDark
(
d
,
e
),
h
=-
1
;
1
>=
h
;
h
++
)
if
(
!
(
0
>
d
+
h
||
d
+
h
>=
b
))
for
(
var
i
=-
1
;
1
>=
i
;
i
++
)
0
>
e
+
i
||
e
+
i
>=
b
||
(
0
!=
h
||
0
!=
i
)
&&
g
==
a
.
isDark
(
d
+
h
,
e
+
i
)
&&
f
++
;
f
>
5
&&
(
c
+=
3
+
f
-
5
)}
for
(
var
d
=
0
;
b
-
1
>
d
;
d
++
)
for
(
var
e
=
0
;
b
-
1
>
e
;
e
++
){
var
j
=
0
;
a
.
isDark
(
d
,
e
)
&&
j
++
,
a
.
isDark
(
d
+
1
,
e
)
&&
j
++
,
a
.
isDark
(
d
,
e
+
1
)
&&
j
++
,
a
.
isDark
(
d
+
1
,
e
+
1
)
&&
j
++
,(
0
==
j
||
4
==
j
)
&&
(
c
+=
3
)}
for
(
var
d
=
0
;
b
>
d
;
d
++
)
for
(
var
e
=
0
;
b
-
6
>
e
;
e
++
)
a
.
isDark
(
d
,
e
)
&&!
a
.
isDark
(
d
,
e
+
1
)
&&
a
.
isDark
(
d
,
e
+
2
)
&&
a
.
isDark
(
d
,
e
+
3
)
&&
a
.
isDark
(
d
,
e
+
4
)
&&!
a
.
isDark
(
d
,
e
+
5
)
&&
a
.
isDark
(
d
,
e
+
6
)
&&
(
c
+=
40
);
for
(
var
e
=
0
;
b
>
e
;
e
++
)
for
(
var
d
=
0
;
b
-
6
>
d
;
d
++
)
a
.
isDark
(
d
,
e
)
&&!
a
.
isDark
(
d
+
1
,
e
)
&&
a
.
isDark
(
d
+
2
,
e
)
&&
a
.
isDark
(
d
+
3
,
e
)
&&
a
.
isDark
(
d
+
4
,
e
)
&&!
a
.
isDark
(
d
+
5
,
e
)
&&
a
.
isDark
(
d
+
6
,
e
)
&&
(
c
+=
40
);
for
(
var
k
=
0
,
e
=
0
;
b
>
e
;
e
++
)
for
(
var
d
=
0
;
b
>
d
;
d
++
)
a
.
isDark
(
d
,
e
)
&&
k
++
;
var
l
=
Math
.
abs
(
100
*
k
/
b
/
b
-
50
)
/
5
;
return
c
+=
10
*
l
}},
g
=
{
glog
:
function
(
a
){
if
(
1
>
a
)
throw
new
Error
(
"glog("
+
a
+
")"
);
return
g
.
LOG_TABLE
[
a
]},
gexp
:
function
(
a
){
for
(;
0
>
a
;)
a
+=
255
;
for
(;
a
>=
256
;)
a
-=
255
;
return
g
.
EXP_TABLE
[
a
]},
EXP_TABLE
:
new
Array
(
256
),
LOG_TABLE
:
new
Array
(
256
)},
h
=
0
;
8
>
h
;
h
++
)
g
.
EXP_TABLE
[
h
]
=
1
<<
h
;
for
(
var
h
=
8
;
256
>
h
;
h
++
)
g
.
EXP_TABLE
[
h
]
=
g
.
EXP_TABLE
[
h
-
4
]
^
g
.
EXP_TABLE
[
h
-
5
]
^
g
.
EXP_TABLE
[
h
-
6
]
^
g
.
EXP_TABLE
[
h
-
8
];
for
(
var
h
=
0
;
255
>
h
;
h
++
)
g
.
LOG_TABLE
[
g
.
EXP_TABLE
[
h
]]
=
h
;
i
.
prototype
=
{
get
:
function
(
a
){
return
this
.
num
[
a
]},
getLength
:
function
(){
return
this
.
num
.
length
},
multiply
:
function
(
a
){
for
(
var
b
=
new
Array
(
this
.
getLength
()
+
a
.
getLength
()
-
1
),
c
=
0
;
c
<
this
.
getLength
();
c
++
)
for
(
var
d
=
0
;
d
<
a
.
getLength
();
d
++
)
b
[
c
+
d
]
^=
g
.
gexp
(
g
.
glog
(
this
.
get
(
c
))
+
g
.
glog
(
a
.
get
(
d
)));
return
new
i
(
b
,
0
)},
mod
:
function
(
a
){
if
(
this
.
getLength
()
-
a
.
getLength
()
<
0
)
return
this
;
for
(
var
b
=
g
.
glog
(
this
.
get
(
0
))
-
g
.
glog
(
a
.
get
(
0
)),
c
=
new
Array
(
this
.
getLength
()),
d
=
0
;
d
<
this
.
getLength
();
d
++
)
c
[
d
]
=
this
.
get
(
d
);
for
(
var
d
=
0
;
d
<
a
.
getLength
();
d
++
)
c
[
d
]
^=
g
.
gexp
(
g
.
glog
(
a
.
get
(
d
))
+
b
);
return
new
i
(
c
,
0
).
mod
(
a
)}},
j
.
RS_BLOCK_TABLE
=
[[
1
,
26
,
19
],[
1
,
26
,
16
],[
1
,
26
,
13
],[
1
,
26
,
9
],[
1
,
44
,
34
],[
1
,
44
,
28
],[
1
,
44
,
22
],[
1
,
44
,
16
],[
1
,
70
,
55
],[
1
,
70
,
44
],[
2
,
35
,
17
],[
2
,
35
,
13
],[
1
,
100
,
80
],[
2
,
50
,
32
],[
2
,
50
,
24
],[
4
,
25
,
9
],[
1
,
134
,
108
],[
2
,
67
,
43
],[
2
,
33
,
15
,
2
,
34
,
16
],[
2
,
33
,
11
,
2
,
34
,
12
],[
2
,
86
,
68
],[
4
,
43
,
27
],[
4
,
43
,
19
],[
4
,
43
,
15
],[
2
,
98
,
78
],[
4
,
49
,
31
],[
2
,
32
,
14
,
4
,
33
,
15
],[
4
,
39
,
13
,
1
,
40
,
14
],[
2
,
121
,
97
],[
2
,
60
,
38
,
2
,
61
,
39
],[
4
,
40
,
18
,
2
,
41
,
19
],[
4
,
40
,
14
,
2
,
41
,
15
],[
2
,
146
,
116
],[
3
,
58
,
36
,
2
,
59
,
37
],[
4
,
36
,
16
,
4
,
37
,
17
],[
4
,
36
,
12
,
4
,
37
,
13
],[
2
,
86
,
68
,
2
,
87
,
69
],[
4
,
69
,
43
,
1
,
70
,
44
],[
6
,
43
,
19
,
2
,
44
,
20
],[
6
,
43
,
15
,
2
,
44
,
16
],[
4
,
101
,
81
],[
1
,
80
,
50
,
4
,
81
,
51
],[
4
,
50
,
22
,
4
,
51
,
23
],[
3
,
36
,
12
,
8
,
37
,
13
],[
2
,
116
,
92
,
2
,
117
,
93
],[
6
,
58
,
36
,
2
,
59
,
37
],[
4
,
46
,
20
,
6
,
47
,
21
],[
7
,
42
,
14
,
4
,
43
,
15
],[
4
,
133
,
107
],[
8
,
59
,
37
,
1
,
60
,
38
],[
8
,
44
,
20
,
4
,
45
,
21
],[
12
,
33
,
11
,
4
,
34
,
12
],[
3
,
145
,
115
,
1
,
146
,
116
],[
4
,
64
,
40
,
5
,
65
,
41
],[
11
,
36
,
16
,
5
,
37
,
17
],[
11
,
36
,
12
,
5
,
37
,
13
],[
5
,
109
,
87
,
1
,
110
,
88
],[
5
,
65
,
41
,
5
,
66
,
42
],[
5
,
54
,
24
,
7
,
55
,
25
],[
11
,
36
,
12
],[
5
,
122
,
98
,
1
,
123
,
99
],[
7
,
73
,
45
,
3
,
74
,
46
],[
15
,
43
,
19
,
2
,
44
,
20
],[
3
,
45
,
15
,
13
,
46
,
16
],[
1
,
135
,
107
,
5
,
136
,
108
],[
10
,
74
,
46
,
1
,
75
,
47
],[
1
,
50
,
22
,
15
,
51
,
23
],[
2
,
42
,
14
,
17
,
43
,
15
],[
5
,
150
,
120
,
1
,
151
,
121
],[
9
,
69
,
43
,
4
,
70
,
44
],[
17
,
50
,
22
,
1
,
51
,
23
],[
2
,
42
,
14
,
19
,
43
,
15
],[
3
,
141
,
113
,
4
,
142
,
114
],[
3
,
70
,
44
,
11
,
71
,
45
],[
17
,
47
,
21
,
4
,
48
,
22
],[
9
,
39
,
13
,
16
,
40
,
14
],[
3
,
135
,
107
,
5
,
136
,
108
],[
3
,
67
,
41
,
13
,
68
,
42
],[
15
,
54
,
24
,
5
,
55
,
25
],[
15
,
43
,
15
,
10
,
44
,
16
],[
4
,
144
,
116
,
4
,
145
,
117
],[
17
,
68
,
42
],[
17
,
50
,
22
,
6
,
51
,
23
],[
19
,
46
,
16
,
6
,
47
,
17
],[
2
,
139
,
111
,
7
,
140
,
112
],[
17
,
74
,
46
],[
7
,
54
,
24
,
16
,
55
,
25
],[
34
,
37
,
13
],[
4
,
151
,
121
,
5
,
152
,
122
],[
4
,
75
,
47
,
14
,
76
,
48
],[
11
,
54
,
24
,
14
,
55
,
25
],[
16
,
45
,
15
,
14
,
46
,
16
],[
6
,
147
,
117
,
4
,
148
,
118
],[
6
,
73
,
45
,
14
,
74
,
46
],[
11
,
54
,
24
,
16
,
55
,
25
],[
30
,
46
,
16
,
2
,
47
,
17
],[
8
,
132
,
106
,
4
,
133
,
107
],[
8
,
75
,
47
,
13
,
76
,
48
],[
7
,
54
,
24
,
22
,
55
,
25
],[
22
,
45
,
15
,
13
,
46
,
16
],[
10
,
142
,
114
,
2
,
143
,
115
],[
19
,
74
,
46
,
4
,
75
,
47
],[
28
,
50
,
22
,
6
,
51
,
23
],[
33
,
46
,
16
,
4
,
47
,
17
],[
8
,
152
,
122
,
4
,
153
,
123
],[
22
,
73
,
45
,
3
,
74
,
46
],[
8
,
53
,
23
,
26
,
54
,
24
],[
12
,
45
,
15
,
28
,
46
,
16
],[
3
,
147
,
117
,
10
,
148
,
118
],[
3
,
73
,
45
,
23
,
74
,
46
],[
4
,
54
,
24
,
31
,
55
,
25
],[
11
,
45
,
15
,
31
,
46
,
16
],[
7
,
146
,
116
,
7
,
147
,
117
],[
21
,
73
,
45
,
7
,
74
,
46
],[
1
,
53
,
23
,
37
,
54
,
24
],[
19
,
45
,
15
,
26
,
46
,
16
],[
5
,
145
,
115
,
10
,
146
,
116
],[
19
,
75
,
47
,
10
,
76
,
48
],[
15
,
54
,
24
,
25
,
55
,
25
],[
23
,
45
,
15
,
25
,
46
,
16
],[
13
,
145
,
115
,
3
,
146
,
116
],[
2
,
74
,
46
,
29
,
75
,
47
],[
42
,
54
,
24
,
1
,
55
,
25
],[
23
,
45
,
15
,
28
,
46
,
16
],[
17
,
145
,
115
],[
10
,
74
,
46
,
23
,
75
,
47
],[
10
,
54
,
24
,
35
,
55
,
25
],[
19
,
45
,
15
,
35
,
46
,
16
],[
17
,
145
,
115
,
1
,
146
,
116
],[
14
,
74
,
46
,
21
,
75
,
47
],[
29
,
54
,
24
,
19
,
55
,
25
],[
11
,
45
,
15
,
46
,
46
,
16
],[
13
,
145
,
115
,
6
,
146
,
116
],[
14
,
74
,
46
,
23
,
75
,
47
],[
44
,
54
,
24
,
7
,
55
,
25
],[
59
,
46
,
16
,
1
,
47
,
17
],[
12
,
151
,
121
,
7
,
152
,
122
],[
12
,
75
,
47
,
26
,
76
,
48
],[
39
,
54
,
24
,
14
,
55
,
25
],[
22
,
45
,
15
,
41
,
46
,
16
],[
6
,
151
,
121
,
14
,
152
,
122
],[
6
,
75
,
47
,
34
,
76
,
48
],[
46
,
54
,
24
,
10
,
55
,
25
],[
2
,
45
,
15
,
64
,
46
,
16
],[
17
,
152
,
122
,
4
,
153
,
123
],[
29
,
74
,
46
,
14
,
75
,
47
],[
49
,
54
,
24
,
10
,
55
,
25
],[
24
,
45
,
15
,
46
,
46
,
16
],[
4
,
152
,
122
,
18
,
153
,
123
],[
13
,
74
,
46
,
32
,
75
,
47
],[
48
,
54
,
24
,
14
,
55
,
25
],[
42
,
45
,
15
,
32
,
46
,
16
],[
20
,
147
,
117
,
4
,
148
,
118
],[
40
,
75
,
47
,
7
,
76
,
48
],[
43
,
54
,
24
,
22
,
55
,
25
],[
10
,
45
,
15
,
67
,
46
,
16
],[
19
,
148
,
118
,
6
,
149
,
119
],[
18
,
75
,
47
,
31
,
76
,
48
],[
34
,
54
,
24
,
34
,
55
,
25
],[
20
,
45
,
15
,
61
,
46
,
16
]],
j
.
getRSBlocks
=
function
(
a
,
b
){
var
c
=
j
.
getRsBlockTable
(
a
,
b
);
if
(
void
0
==
c
)
throw
new
Error
(
"bad rs block @ typeNumber:"
+
a
+
"/errorCorrectLevel:"
+
b
);
for
(
var
d
=
c
.
length
/
3
,
e
=
[],
f
=
0
;
d
>
f
;
f
++
)
for
(
var
g
=
c
[
3
*
f
+
0
],
h
=
c
[
3
*
f
+
1
],
i
=
c
[
3
*
f
+
2
],
k
=
0
;
g
>
k
;
k
++
)
e
.
push
(
new
j
(
h
,
i
));
return
e
},
j
.
getRsBlockTable
=
function
(
a
,
b
){
switch
(
b
){
case
d
.
L
:
return
j
.
RS_BLOCK_TABLE
[
4
*
(
a
-
1
)
+
0
];
case
d
.
M
:
return
j
.
RS_BLOCK_TABLE
[
4
*
(
a
-
1
)
+
1
];
case
d
.
Q
:
return
j
.
RS_BLOCK_TABLE
[
4
*
(
a
-
1
)
+
2
];
case
d
.
H
:
return
j
.
RS_BLOCK_TABLE
[
4
*
(
a
-
1
)
+
3
];
default
:
return
void
0
}},
k
.
prototype
=
{
get
:
function
(
a
){
var
b
=
Math
.
floor
(
a
/
8
);
return
1
==
(
1
&
this
.
buffer
[
b
]
>>>
7
-
a
%
8
)},
put
:
function
(
a
,
b
){
for
(
var
c
=
0
;
b
>
c
;
c
++
)
this
.
putBit
(
1
==
(
1
&
a
>>>
b
-
c
-
1
))},
getLengthInBits
:
function
(){
return
this
.
length
},
putBit
:
function
(
a
){
var
b
=
Math
.
floor
(
this
.
length
/
8
);
this
.
buffer
.
length
<=
b
&&
this
.
buffer
.
push
(
0
),
a
&&
(
this
.
buffer
[
b
]
|=
128
>>>
this
.
length
%
8
),
this
.
length
++
}};
var
l
=
[[
17
,
14
,
11
,
7
],[
32
,
26
,
20
,
14
],[
53
,
42
,
32
,
24
],[
78
,
62
,
46
,
34
],[
106
,
84
,
60
,
44
],[
134
,
106
,
74
,
58
],[
154
,
122
,
86
,
64
],[
192
,
152
,
108
,
84
],[
230
,
180
,
130
,
98
],[
271
,
213
,
151
,
119
],[
321
,
251
,
177
,
137
],[
367
,
287
,
203
,
155
],[
425
,
331
,
241
,
177
],[
458
,
362
,
258
,
194
],[
520
,
412
,
292
,
220
],[
586
,
450
,
322
,
250
],[
644
,
504
,
364
,
280
],[
718
,
560
,
394
,
310
],[
792
,
624
,
442
,
338
],[
858
,
666
,
482
,
382
],[
929
,
711
,
509
,
403
],[
1003
,
779
,
565
,
439
],[
1091
,
857
,
611
,
461
],[
1171
,
911
,
661
,
511
],[
1273
,
997
,
715
,
535
],[
1367
,
1059
,
751
,
593
],[
1465
,
1125
,
805
,
625
],[
1528
,
1190
,
868
,
658
],[
1628
,
1264
,
908
,
698
],[
1732
,
1370
,
982
,
742
],[
1840
,
1452
,
1030
,
790
],[
1952
,
1538
,
1112
,
842
],[
2068
,
1628
,
1168
,
898
],[
2188
,
1722
,
1228
,
958
],[
2303
,
1809
,
1283
,
983
],[
2431
,
1911
,
1351
,
1051
],[
2563
,
1989
,
1423
,
1093
],[
2699
,
2099
,
1499
,
1139
],[
2809
,
2213
,
1579
,
1219
],[
2953
,
2331
,
1663
,
1273
]],
o
=
function
(){
var
a
=
function
(
a
,
b
){
this
.
_el
=
a
,
this
.
_htOption
=
b
};
return
a
.
prototype
.
draw
=
function
(
a
){
function
g
(
a
,
b
){
var
c
=
document
.
createElementNS
(
"http://www.w3.org/2000/svg"
,
a
);
for
(
var
d
in
b
)
b
.
hasOwnProperty
(
d
)
&&
c
.
setAttribute
(
d
,
b
[
d
]);
return
c
}
var
b
=
this
.
_htOption
,
c
=
this
.
_el
,
d
=
a
.
getModuleCount
();
Math
.
floor
(
b
.
width
/
d
),
Math
.
floor
(
b
.
height
/
d
),
this
.
clear
();
var
h
=
g
(
"svg"
,{
viewBox
:
"0 0 "
+
String
(
d
)
+
" "
+
String
(
d
),
width
:
"100%"
,
height
:
"100%"
,
fill
:
b
.
colorLight
});
h
.
setAttributeNS
(
"http://www.w3.org/2000/xmlns/"
,
"xmlns:xlink"
,
"http://www.w3.org/1999/xlink"
),
c
.
appendChild
(
h
),
h
.
appendChild
(
g
(
"rect"
,{
fill
:
b
.
colorDark
,
width
:
"1"
,
height
:
"1"
,
id
:
"template"
}));
for
(
var
i
=
0
;
d
>
i
;
i
++
)
for
(
var
j
=
0
;
d
>
j
;
j
++
)
if
(
a
.
isDark
(
i
,
j
)){
var
k
=
g
(
"use"
,{
x
:
String
(
i
),
y
:
String
(
j
)});
k
.
setAttributeNS
(
"http://www.w3.org/1999/xlink"
,
"href"
,
"#template"
),
h
.
appendChild
(
k
)}},
a
.
prototype
.
clear
=
function
(){
for
(;
this
.
_el
.
hasChildNodes
();)
this
.
_el
.
removeChild
(
this
.
_el
.
lastChild
)},
a
}(),
p
=
"svg"
===
document
.
documentElement
.
tagName
.
toLowerCase
(),
q
=
p
?
o
:
m
()?
function
(){
function
a
(){
this
.
_elImage
.
src
=
this
.
_elCanvas
.
toDataURL
(
"image/png"
),
this
.
_elImage
.
style
.
display
=
"block"
,
this
.
_elCanvas
.
style
.
display
=
"none"
}
function
d
(
a
,
b
){
var
c
=
this
;
if
(
c
.
_fFail
=
b
,
c
.
_fSuccess
=
a
,
null
===
c
.
_bSupportDataURI
){
var
d
=
document
.
createElement
(
"img"
),
e
=
function
(){
c
.
_bSupportDataURI
=!
1
,
c
.
_fFail
&&
_fFail
.
call
(
c
)},
f
=
function
(){
c
.
_bSupportDataURI
=!
0
,
c
.
_fSuccess
&&
c
.
_fSuccess
.
call
(
c
)};
return
d
.
onabort
=
e
,
d
.
onerror
=
e
,
d
.
onload
=
f
,
d
.
src
=
"data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
,
void
0
}
c
.
_bSupportDataURI
===!
0
&&
c
.
_fSuccess
?
c
.
_fSuccess
.
call
(
c
):
c
.
_bSupportDataURI
===!
1
&&
c
.
_fFail
&&
c
.
_fFail
.
call
(
c
)}
if
(
this
.
_android
&&
this
.
_android
<=
2.1
){
var
b
=
1
/
window
.
devicePixelRatio
,
c
=
CanvasRenderingContext2D
.
prototype
.
drawImage
;
CanvasRenderingContext2D
.
prototype
.
drawImage
=
function
(
a
,
d
,
e
,
f
,
g
,
h
,
i
,
j
){
if
(
"nodeName"
in
a
&&
/img/i
.
test
(
a
.
nodeName
))
for
(
var
l
=
arguments
.
length
-
1
;
l
>=
1
;
l
--
)
arguments
[
l
]
=
arguments
[
l
]
*
b
;
else
"undefined"
==
typeof
j
&&
(
arguments
[
1
]
*=
b
,
arguments
[
2
]
*=
b
,
arguments
[
3
]
*=
b
,
arguments
[
4
]
*=
b
);
c
.
apply
(
this
,
arguments
)}}
var
e
=
function
(
a
,
b
){
this
.
_bIsPainted
=!
1
,
this
.
_android
=
n
(),
this
.
_htOption
=
b
,
this
.
_elCanvas
=
document
.
createElement
(
"canvas"
),
this
.
_elCanvas
.
width
=
b
.
width
,
this
.
_elCanvas
.
height
=
b
.
height
,
a
.
appendChild
(
this
.
_elCanvas
),
this
.
_el
=
a
,
this
.
_oContext
=
this
.
_elCanvas
.
getContext
(
"2d"
),
this
.
_bIsPainted
=!
1
,
this
.
_elImage
=
document
.
createElement
(
"img"
),
this
.
_elImage
.
style
.
display
=
"none"
,
this
.
_el
.
appendChild
(
this
.
_elImage
),
this
.
_bSupportDataURI
=
null
};
return
e
.
prototype
.
draw
=
function
(
a
){
var
b
=
this
.
_elImage
,
c
=
this
.
_oContext
,
d
=
this
.
_htOption
,
e
=
a
.
getModuleCount
(),
f
=
d
.
width
/
e
,
g
=
d
.
height
/
e
,
h
=
Math
.
round
(
f
),
i
=
Math
.
round
(
g
);
b
.
style
.
display
=
"none"
,
this
.
clear
();
for
(
var
j
=
0
;
e
>
j
;
j
++
)
for
(
var
k
=
0
;
e
>
k
;
k
++
){
var
l
=
a
.
isDark
(
j
,
k
),
m
=
k
*
f
,
n
=
j
*
g
;
c
.
strokeStyle
=
l
?
d
.
colorDark
:
d
.
colorLight
,
c
.
lineWidth
=
1
,
c
.
fillStyle
=
l
?
d
.
colorDark
:
d
.
colorLight
,
c
.
fillRect
(
m
,
n
,
f
,
g
),
c
.
strokeRect
(
Math
.
floor
(
m
)
+
.
5
,
Math
.
floor
(
n
)
+
.
5
,
h
,
i
),
c
.
strokeRect
(
Math
.
ceil
(
m
)
-
.
5
,
Math
.
ceil
(
n
)
-
.
5
,
h
,
i
)}
this
.
_bIsPainted
=!
0
},
e
.
prototype
.
makeImage
=
function
(){
this
.
_bIsPainted
&&
d
.
call
(
this
,
a
)},
e
.
prototype
.
isPainted
=
function
(){
return
this
.
_bIsPainted
},
e
.
prototype
.
clear
=
function
(){
this
.
_oContext
.
clearRect
(
0
,
0
,
this
.
_elCanvas
.
width
,
this
.
_elCanvas
.
height
),
this
.
_bIsPainted
=!
1
},
e
.
prototype
.
round
=
function
(
a
){
return
a
?
Math
.
floor
(
1
e3
*
a
)
/
1
e3
:
a
},
e
}():
function
(){
var
a
=
function
(
a
,
b
){
this
.
_el
=
a
,
this
.
_htOption
=
b
};
return
a
.
prototype
.
draw
=
function
(
a
){
for
(
var
b
=
this
.
_htOption
,
c
=
this
.
_el
,
d
=
a
.
getModuleCount
(),
e
=
Math
.
floor
(
b
.
width
/
d
),
f
=
Math
.
floor
(
b
.
height
/
d
),
g
=
[
'<table style="border:0;border-collapse:collapse;">'
],
h
=
0
;
d
>
h
;
h
++
){
g
.
push
(
"<tr>"
);
for
(
var
i
=
0
;
d
>
i
;
i
++
)
g
.
push
(
'<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:'
+
e
+
"px;height:"
+
f
+
"px;background-color:"
+
(
a
.
isDark
(
h
,
i
)?
b
.
colorDark
:
b
.
colorLight
)
+
';"></td>'
);
g
.
push
(
"</tr>"
)}
g
.
push
(
"</table>"
),
c
.
innerHTML
=
g
.
join
(
""
);
var
j
=
c
.
childNodes
[
0
],
k
=
(
b
.
width
-
j
.
offsetWidth
)
/
2
,
l
=
(
b
.
height
-
j
.
offsetHeight
)
/
2
;
k
>
0
&&
l
>
0
&&
(
j
.
style
.
margin
=
l
+
"px "
+
k
+
"px"
)},
a
.
prototype
.
clear
=
function
(){
this
.
_el
.
innerHTML
=
""
},
a
}();
QRCode
=
function
(
a
,
b
){
if
(
this
.
_htOption
=
{
width
:
256
,
height
:
256
,
typeNumber
:
4
,
colorDark
:
"#000000"
,
colorLight
:
"#ffffff"
,
correctLevel
:
d
.
H
},
"string"
==
typeof
b
&&
(
b
=
{
text
:
b
}),
b
)
for
(
var
c
in
b
)
this
.
_htOption
[
c
]
=
b
[
c
];
"string"
==
typeof
a
&&
(
a
=
document
.
getElementById
(
a
)),
this
.
_android
=
n
(),
this
.
_el
=
a
,
this
.
_oQRCode
=
null
,
this
.
_oDrawing
=
new
q
(
this
.
_el
,
this
.
_htOption
),
this
.
_htOption
.
text
&&
this
.
makeCode
(
this
.
_htOption
.
text
)},
QRCode
.
prototype
.
makeCode
=
function
(
a
){
this
.
_oQRCode
=
new
b
(
r
(
a
,
this
.
_htOption
.
correctLevel
),
this
.
_htOption
.
correctLevel
),
this
.
_oQRCode
.
addData
(
a
),
this
.
_oQRCode
.
make
(),
this
.
_el
.
title
=
a
,
this
.
_oDrawing
.
draw
(
this
.
_oQRCode
),
this
.
makeImage
()},
QRCode
.
prototype
.
makeImage
=
function
(){
"function"
==
typeof
this
.
_oDrawing
.
makeImage
&&
(
!
this
.
_android
||
this
.
_android
>=
3
)
&&
this
.
_oDrawing
.
makeImage
()},
QRCode
.
prototype
.
clear
=
function
(){
this
.
_oDrawing
.
clear
()},
QRCode
.
CorrectLevel
=
d
}();
\ No newline at end of file
apps/users/api.py
View file @
0bbfc743
...
...
@@ -16,7 +16,7 @@ from .tasks import write_login_log_async
from
.models
import
User
,
UserGroup
from
.permissions
import
IsSuperUser
,
IsValidUser
,
IsCurrentUserOrReadOnly
,
\
IsSuperUserOrAppUser
from
.utils
import
check_user_valid
,
generate_token
from
.utils
import
check_user_valid
,
generate_token
,
get_login_ip
,
check_otp_code
from
common.mixins
import
IDInFilterMixin
from
common.utils
import
get_logger
...
...
@@ -129,48 +129,76 @@ class UserToken(APIView):
class
UserProfile
(
APIView
):
permission_classes
=
(
IsValidUser
,)
serializer_class
=
UserSerializer
def
get
(
self
,
request
):
return
Response
(
request
.
user
.
to_json
())
# return Response(request.user.to_json())
return
Response
(
self
.
serializer_class
(
request
.
user
)
.
data
)
def
post
(
self
,
request
):
return
Response
(
request
.
user
.
to_json
()
)
return
Response
(
self
.
serializer_class
(
request
.
user
)
.
data
)
class
UserAuthApi
(
APIView
):
permission_classes
=
(
AllowAny
,)
serializer_class
=
UserSerializer
def
post
(
self
,
request
):
otp_check
=
request
.
data
.
get
(
'otp_check'
,
None
)
if
otp_check
:
# otp验证
return
self
.
check_auth_otp
(
request
)
else
:
# password验证
return
self
.
check_auth_password
(
request
)
def
check_auth_password
(
self
,
request
):
user
,
msg
=
self
.
check_user_valid
(
request
)
if
user
:
token
=
generate_token
(
request
,
user
)
if
not
user
.
otp_enabled
:
self
.
write_login_log
(
request
,
user
)
return
Response
({
'token'
:
token
,
'user'
:
self
.
serializer_class
(
user
)
.
data
})
else
:
return
Response
({
'msg'
:
msg
},
status
=
401
)
def
check_auth_otp
(
self
,
request
):
otp_code
=
request
.
data
.
get
(
'otp_code'
,
''
)
user
,
msg
=
self
.
check_user_valid
(
request
)
if
user
:
token
=
generate_token
(
request
,
user
)
if
check_otp_code
(
user
.
otp_secret_key
,
otp_code
):
self
.
write_login_log
(
request
,
user
)
return
Response
({
'token'
:
token
,
'user'
:
self
.
serializer_class
(
user
)
.
data
})
return
Response
({
'msg'
:
msg
},
status
=
401
)
@staticmethod
def
check_user_valid
(
request
):
username
=
request
.
data
.
get
(
'username'
,
''
)
password
=
request
.
data
.
get
(
'password'
,
''
)
public_key
=
request
.
data
.
get
(
'public_key'
,
''
)
login_type
=
request
.
data
.
get
(
'login_type'
,
''
)
user
,
msg
=
check_user_valid
(
username
=
username
,
password
=
password
,
public_key
=
public_key
)
return
user
,
msg
@staticmethod
def
write_login_log
(
request
,
user
):
login_ip
=
request
.
data
.
get
(
'remote_addr'
,
None
)
login_type
=
request
.
data
.
get
(
'login_type'
,
''
)
user_agent
=
request
.
data
.
get
(
'HTTP_USER_AGENT'
,
''
)
if
not
login_ip
:
x_forwarded_for
=
request
.
META
.
get
(
'HTTP_X_FORWARDED_FOR'
,
''
)
.
split
(
','
)
login_ip
=
get_login_ip
(
request
)
if
x_forwarded_for
and
x_forwarded_for
[
0
]:
login_ip
=
x_forwarded_for
[
0
]
else
:
login_ip
=
request
.
META
.
get
(
"REMOTE_ADDR"
)
user
,
msg
=
check_user_valid
(
username
=
username
,
password
=
password
,
public_key
=
public_key
write_login_log_async
.
delay
(
user
.
username
,
ip
=
login_ip
,
type
=
login_type
,
user_agent
=
user_agent
,
)
if
user
:
token
=
generate_token
(
request
,
user
)
write_login_log_async
.
delay
(
user
.
username
,
ip
=
login_ip
,
type
=
login_type
,
user_agent
=
user_agent
,
)
return
Response
({
'token'
:
token
,
'user'
:
user
.
to_json
()})
else
:
return
Response
({
'msg'
:
msg
},
status
=
401
)
class
UserConnectionTokenApi
(
APIView
):
permission_classes
=
(
IsSuperUserOrAppUser
,)
...
...
apps/users/forms.py
View file @
0bbfc743
...
...
@@ -18,6 +18,18 @@ class UserLoginForm(AuthenticationForm):
captcha
=
CaptchaField
()
class
UserCheckPasswordForm
(
forms
.
Form
):
username
=
forms
.
CharField
(
label
=
_
(
'Username'
),
max_length
=
100
)
password
=
forms
.
CharField
(
label
=
_
(
'Password'
),
widget
=
forms
.
PasswordInput
,
max_length
=
128
,
strip
=
False
)
class
UserCheckOtpCodeForm
(
forms
.
Form
):
otp_code
=
forms
.
CharField
(
label
=
_
(
'Otp_code'
),
max_length
=
6
)
class
UserCreateUpdateForm
(
forms
.
ModelForm
):
role_choices
=
((
i
,
n
)
for
i
,
n
in
User
.
ROLE_CHOICES
if
i
!=
User
.
ROLE_APP
)
password
=
forms
.
CharField
(
...
...
apps/users/models/user.py
View file @
0bbfc743
...
...
@@ -45,7 +45,7 @@ class User(AbstractUser):
wechat
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
verbose_name
=
_
(
'Wechat'
))
phone
=
models
.
CharField
(
max_length
=
20
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Phone'
))
otp_level
=
models
.
SmallIntegerField
(
default
=
0
,
choices
=
OTP_LEVEL_CHOICES
,
verbose_name
=
_
(
'Enable OTP'
))
otp_secret_key
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
)
otp_secret_key
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
,
null
=
True
)
# Todo: Auto generate key, let user download
_private_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'Private key'
))
_public_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'Public key'
))
...
...
@@ -211,15 +211,20 @@ class User(AbstractUser):
def
otp_enabled
(
self
):
return
self
.
otp_level
>
0
def
enabled_otp
(
self
):
self
.
otp_level
=
1
@property
def
otp_force_enabled
(
self
):
return
self
.
otp_level
==
2
def
enable_otp
(
self
):
if
not
self
.
otp_force_enabled
:
self
.
otp_level
=
1
def
force_enable_otp
(
self
):
self
.
otp_level
=
2
@property
def
otp_force_enabled
(
self
):
return
self
.
otp_level
==
2
def
disable_otp
(
self
):
self
.
otp_level
=
0
self
.
otp_secret_key
=
''
def
to_json
(
self
):
return
OrderedDict
({
...
...
@@ -233,6 +238,7 @@ class User(AbstractUser):
'groups'
:
[
group
.
name
for
group
in
self
.
groups
.
all
()],
'wechat'
:
self
.
wechat
,
'phone'
:
self
.
phone
,
'otp_level'
:
self
.
otp_level
,
'comment'
:
self
.
comment
,
'date_expired'
:
self
.
date_expired
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
self
.
date_expired
is
not
None
else
None
})
...
...
apps/users/serializers.py
View file @
0bbfc743
...
...
@@ -19,7 +19,10 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class
Meta
:
model
=
User
list_serializer_class
=
BulkListSerializer
exclude
=
[
'first_name'
,
'last_name'
,
'password'
,
'_private_key'
,
'_public_key'
]
exclude
=
[
'first_name'
,
'last_name'
,
'password'
,
'_private_key'
,
'_public_key'
,
'otp_secret_key'
,
'user_permissions'
]
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
super
(
UserSerializer
,
self
)
.
get_field_names
(
declared_fields
,
info
)
...
...
apps/users/templates/users/_base_otp.html
0 → 100644
View file @
0bbfc743
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"UTF-8"
>
<title>
Jumpserver
</title>
<link
rel=
"shortcut icon"
href=
"{% static "
img
/
facio
.
ico
"
%}"
type=
"image/x-icon"
>
<link
rel=
"stylesheet"
href=
"{% static 'fonts/font_otp/iconfont.css' %}"
/>
<link
rel=
"stylesheet"
href=
"{% static 'css/otp.css' %}"
/>
<script
src=
"{% static 'js/jquery-2.1.1.js' %}"
></script>
<script
src=
"{% static "
js
/
plugins
/
qrcode
/
qrcode
.
min
.
js
"
%}"
></script>
</head>
<body>
<!--头部-->
<header>
<div
class=
"logo"
>
<a
href=
"{% url 'index' %}"
>
<img
src=
"{% static 'img/logo.png' %}"
alt=
""
width=
"50px"
height=
"50px"
/>
</a>
<a
href=
"{% url 'index' %}"
>
Jumpserver
</a>
</div>
<div>
<a
href=
"{% url 'index' %}"
>
首页
</a>
<b>
丨
</b>
<a
href=
"#"
>
帮助中心
</a>
<b>
丨
</b>
<a
href=
"https://www.github.com/jumpserver/"
>
GitHub
</a>
</div>
</header>
<!--内容-->
<article>
<div
class=
"clearfix"
>
<ul
class=
"change-color"
>
<li>
<div>
<i
class=
"iconfont icon-step active"
></i>
<span></span>
</div>
<div
class=
"back"
>
验证身份
</div>
</li>
<li>
<div>
<i
class=
"iconfont icon-step2"
></i>
<span></span>
</div>
<div
class=
"back"
>
安装应用
</div>
</li>
<li>
<div>
<i
class=
"iconfont icon-step1"
></i>
<span></span>
</div>
<div
class=
"back"
>
绑定TOTP
</div>
</li>
<li>
<div>
<i
class=
"iconfont icon-duigou"
></i>
</div>
<div>
完成
</div>
</li>
</ul>
</div>
<div
>
<div
class=
"verify"
>
安全令牌验证
账户
<span>
{{ user.username }}
</span>
请按照以下步骤完成绑定操作
</div>
<div
class=
"line"
></div>
{% block content %}
{% endblock %}
</div>
</article>
<footer>
<div
class=
""
style=
"margin-top: 100px;"
>
{% include '_copyright.html' %}
</div>
</footer>
</body>
</html>
apps/users/templates/users/login_otp.html
0 → 100644
View file @
0bbfc743
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"utf-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<title>
Jumpserver
</title>
<link
rel=
"shortcut icon"
href=
"{% static "
img
/
facio
.
ico
"
%}"
type=
"image/x-icon"
>
{% include '_head_css_js.html' %}
<link
href=
"{% static "
css
/
jumpserver
.
css
"
%}"
rel=
"stylesheet"
>
<script
src=
"{% static "
js
/
jumpserver
.
js
"
%}"
></script>
<script
src=
"{% static "
js
/
plugins
/
qrcode
/
qrcode
.
min
.
js
"
%}"
></script>
<style>
.captcha
{
float
:
right
;
}
</style>
</head>
<body
class=
"gray-bg"
>
<div
class=
"loginColumns animated fadeInDown"
>
<div
class=
"row"
>
<div
class=
"col-md-6"
>
<h2
class=
"font-bold"
>
欢迎使用Jumpserver开源堡垒机
</h2>
<p>
全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
</p>
<p>
使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
</p>
<p>
采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。
</p>
<p>
改变世界,从一点点开始。
</p>
</div>
<div
class=
"col-md-6"
>
<div
class=
"ibox-content"
>
<div>
<img
src=
"{% static 'img/logo.png' %}"
width=
"60"
height=
"60"
>
<span
class=
"font-bold text-center"
style=
"font-size: 24px; font-family: inherit; margin-left: 20px"
>
{% trans '二次认证' %}
</span>
</div>
<div
class=
"m-t"
>
<div
class=
"form-group"
>
<p
style=
"margin:30px auto;"
class=
"text-center"
><strong
style=
"color:#000000"
>
账号保护已开启,请根据提示完成以下操作
</strong></p>
<div
class=
"text-center"
>
<img
src=
"{% static 'img/otp_auth.png' %}"
alt=
""
width=
"72px"
height=
"117"
>
</div>
<p
style=
"margin: 30px auto"
>
请在手机中打开Google Authenticator应用,输入6位动态码
</p>
</div>
<form
class=
"m-t"
role=
"form"
method=
"post"
action=
""
>
{% csrf_token %}
{% if 'otp_code' in form.errors %}
<p
class=
"red-fonts"
>
{{ form.otp_code.errors.as_text }}
</p>
{% endif %}
<div
class=
"form-group"
>
<input
type=
"text"
class=
"form-control"
name=
"otp_code"
placeholder=
"{% trans 'Six figures' %}"
required=
""
>
</div>
<button
type=
"submit"
class=
"btn btn-primary block full-width m-b"
>
{% trans 'Next' %}
</button>
<a
href=
"#"
>
<small>
{% trans "Can't provide security? Please contact the administrator" %}
</small>
</a>
</form>
</div>
<p
class=
"m-t"
>
</p>
</div>
</div>
</div>
<hr/>
<div
class=
"row"
>
<div
class=
"col-md-12"
>
{% include '_copyright.html' %}
</div>
</div>
</div>
</body>
</html>
apps/users/templates/users/user_detail.html
View file @
0bbfc743
...
...
@@ -87,10 +87,18 @@
<td>
{% trans 'Role' %}:
</td>
<td><b>
{{ user_object.get_role_display }}
</b></td>
</tr>
{#
<tr>
#}
{#
<td>
{% trans 'Enable OTP' %}:
</td>
#}
{#
<td><b>
{{ user_object.enable_otp|yesno:"Yes,No,Unknown"}}
</b></td>
#}
{#
</tr>
#}
<tr>
<td>
{% trans 'Enable OTP' %}:
</td>
<td><b>
{% if user_object.otp_force_enabled %}
{% trans 'Force enabled' %}
{% elif user_object.otp_enabled%}
{% trans 'Enabled' %}
{% else %}
{% trans 'Disabled' %}
{% endif %}
</b></td>
</tr>
<tr>
<td>
{% trans 'Date expired' %}:
</td>
<td><b>
{{ user_object.date_expired|date:"Y-m-j H:i:s" }}
</b></td>
...
...
@@ -137,22 +145,23 @@
</div>
</div>
</span></td>
</tr>
<tr>
<td>
{% trans 'Force enabled OTP' %}:
</td>
<td><span
class=
"pull-right"
>
<div
class=
"switch"
>
<div
class=
"onoffswitch"
>
<input
type=
"checkbox"
class=
"onoffswitch-checkbox"
{%
if
user_object
.
otp_force_enabled
%}
checked
{%
endif
%}{%
if
request
.
user =
=
user_object
%}
disabled
{%
endif
%}
id=
"force_enable_otp"
>
<label
class=
"onoffswitch-label"
for=
"force_enable_otp"
>
<span
class=
"onoffswitch-inner"
></span>
<span
class=
"onoffswitch-switch"
></span>
</label>
</div>
</div>
</span></td>
</tr>
{#
<tr>
#}
{#
<td>
{% trans 'Enable OTP' %}:
</td>
#}
{#
<td><span
class=
"pull-right"
>
#}
{#
<div
class=
"switch"
>
#}
{#
<div
class=
"onoffswitch"
>
#}
{#
<input
type=
"checkbox"
class=
"onoffswitch-checkbox"
{%
if
user_object
.
enable_otp
%}
checked
{%
endif
%}#}
{#
id=
"enable_otp"
>
#}
{#
<label
class=
"onoffswitch-label"
for=
"enable_otp"
>
#}
{#
<span
class=
"onoffswitch-inner"
></span>
#}
{#
<span
class=
"onoffswitch-switch"
></span>
#}
{#
</label>
#}
{#
</div>
#}
{#
</div>
#}
{#
</span></td>
#}
{#
</tr>
#}
<tr>
<td>
{% trans 'Send reset password mail' %}:
</td>
<td>
...
...
@@ -277,19 +286,28 @@ $(document).ready(function() {
success_message
:
success
});
})
{
#
.
on
(
'click'
,
'#enable_otp'
,
function
()
{
#
}
{
#
var
the_url
=
"{% url 'api-users:user-detail' pk=user_object.id %}"
;
#
}
{
#
var
checked
=
$
(
this
).
prop
(
'checked'
);
#
}
{
#
var
body
=
{
#
}
{
#
'enable_otp'
:
checked
#
}
{
#
};
#
}
{
#
var
success
=
'{% trans "Update successfully!" %}'
;
#
}
{
#
APIUpdateAttr
({
#
}
{
#
url
:
the_url
,
#
}
{
#
body
:
JSON
.
stringify
(
body
),
#
}
{
#
success_message
:
success
#
}
{
#
});
#
}
{
#
});
#
}
.
on
(
'click'
,
'#force_enable_otp'
,
function
()
{
var
the_url
=
"{% url 'api-users:user-detail' pk=user_object.id %}"
;
var
checked
=
$
(
this
).
prop
(
'checked'
);
var
otp_level
;
var
otp_secret_key
;
if
(
checked
){
otp_level
=
2
}
else
{
otp_level
=
0
;
otp_secret_key
=
''
;
}
var
body
=
{
'otp_level'
:
otp_level
,
'otp_secret_key'
:
otp_secret_key
};
var
success
=
'{% trans "Update successfully!" %}'
;
APIUpdateAttr
({
url
:
the_url
,
body
:
JSON
.
stringify
(
body
),
success_message
:
success
});
})
.
on
(
'click'
,
'#btn_join_group'
,
function
()
{
if
(
Object
.
keys
(
jumpserver
.
nodes_selected
).
length
===
0
)
{
return
false
;
...
...
apps/users/templates/users/user_otp_authentication.html
0 → 100644
View file @
0bbfc743
{% extends 'users/_base_otp.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<div
class=
"verify"
>
<p
style=
"margin: 20px auto;"
><strong
style=
"color: #000000"
>
账号保护已开启,请根据提示完成以下操作
</strong></p>
<img
src=
"{% static 'img/otp_auth.png' %}"
alt=
""
width=
"72px"
height=
"117"
>
<p
style=
"margin: 20px auto;"
>
请在手机中打开Google Authenticator应用,输入6为动态码
</p>
</div>
<form
class=
""
role=
"form"
method=
"post"
action=
""
>
{% csrf_token %}
{% if 'otp_code' in form.errors %}
<p
class=
"red-fonts"
>
{{ form.otp_code.errors.as_text }}
</p>
{% endif %}
<div
class=
"form-input"
>
<input
type=
"text"
class=
""
name=
"otp_code"
placeholder=
"{% trans 'Six figures' %}"
required=
""
>
</div>
<button
type=
"submit"
class=
"next"
>
{% trans 'Next' %}
</button>
</form>
<script>
$
(
function
(){
$
(
'.change-color li'
).
eq
(
2
).
remove
();
$
(
'.change-color li:eq(1) div'
).
eq
(
1
).
html
(
'解绑MFA'
)
})
</script>
{% endblock %}
apps/users/templates/users/user_otp_enable_bind.html
0 → 100644
View file @
0bbfc743
{% extends 'users/_base_otp.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<div
class=
"verify"
>
<p
style=
"margin:20px auto;"
><strong
style=
"color: #000000"
>
使用手机 Google Authenticator 应用扫描以下二维码,获取6位验证码
</strong></p>
<div
id=
"qr_code"
></div>
<form
class=
""
role=
"form"
method=
"post"
action=
""
>
{% csrf_token %}
<div
class=
"form-input"
>
<input
type=
"text"
class=
""
name=
"otp_code"
placeholder=
"{% trans 'Six figures' %}"
required=
""
>
</div>
{% if 'otp_code' in form.errors %}
<p
style=
"color: #ed5565"
>
{{ form.otp_code.errors.as_text }}
</p>
{% endif %}
<button
type=
"submit"
class=
"next"
>
{% trans 'Next' %}
</button>
</form>
</div>
<script>
$
(
'.change-color li:eq(1) i'
).
css
(
'color'
,
'#1ab394'
);
$
(
'.change-color li:eq(2) i'
).
css
(
'color'
,
'#1ab394'
);
$
(
document
).
ready
(
function
()
{
// 生成用户绑定otp的二维码
var
qrcode
=
new
QRCode
(
document
.
getElementById
(
'qr_code'
),
{
text
:
"{{ otp_uri|safe}}"
,
width
:
180
,
height
:
180
,
colorDark
:
'#000000'
,
colorLight
:
'#ffffff'
,
correctlevel
:
QRCode
.
CorrectLevel
.
H
});
document
.
getElementById
(
'qr_code'
).
removeAttribute
(
"title"
);
})
</script>
{% endblock %}
apps/users/templates/users/user_otp_enable_install_app.html
0 → 100644
View file @
0bbfc743
{% extends 'users/_base_otp.html' %}
{% load i18n %}
{% load static %}
{% block content %}
<div
class=
"verify"
>
<p
style=
"margin: 20px auto;"
><strong
style=
"color: #000000"
>
请在手机端下载并安装 Google Authenticator 应用
</strong></p>
<div>
<img
src=
"{% static 'img/authenticator_android.png' %}"
width=
"128"
height=
"128"
alt=
""
>
<p>
Android手机下载
</p>
</div>
<div>
<img
src=
"{% static 'img/authenticator_iphone.png' %}"
width=
"128"
height=
"128"
alt=
""
>
<p>
iPhone手机下载
</p>
</div>
<p
style=
"margin: 20px auto;"
></p>
<p
style=
"margin: 20px auto;"
><strong
style=
"color: #000000"
>
安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步)
</strong></p>
</div>
<a
href=
"{% url 'users:user-otp-enable-bind' %}"
class=
"next"
>
{% trans 'Next' %}
</a>
<script>
$
(
function
(){
$
(
'.change-color li:eq(1) i'
).
css
(
'color'
,
'#1ab394'
)
})
</script>
{% endblock %}
apps/users/templates/users/user_password_authentication.html
0 → 100644
View file @
0bbfc743
{% extends 'users/_base_otp.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<form
class=
""
role=
"form"
method=
"post"
action=
""
>
{% csrf_token %}
<div
class=
"form-input"
>
<input
type=
"text"
class=
""
name=
"{{ form.username.html_name }}"
value=
"{{ form.username.value }}"
readonly=
"readonly"
required=
""
>
</div>
<div
class=
"form-input"
>
<input
type=
"password"
class=
""
name=
"{{ form.password.html_name }}"
placeholder=
"{% trans 'Password' %}"
required=
""
>
</div>
{% if 'password' in form.errors %}
<p
class=
"red-fonts"
>
{{ form.password.errors.as_text }}
</p>
{% endif %}
<button
type=
"submit"
class=
"next"
>
{% trans 'Next' %}
</button>
</form>
{% endblock %}
apps/users/templates/users/user_profile.html
View file @
0bbfc743
...
...
@@ -65,7 +65,15 @@
</tr>
<tr>
<td
class=
"text-navy"
>
{% trans 'OTP' %}
</td>
<td>
{{ user.otp_enabled|yesno:"Yes,No,Unkown" }}
</td>
<td>
{% if user.otp_force_enabled %}
{% trans 'Force enable' %}
{% elif user.otp_enabled%}
{% trans 'Enable' %}
{% else %}
{% trans 'Disable' %}
{% endif %}
</td>
</tr>
<tr>
<td
class=
"text-navy"
>
{% trans 'Public key' %}
</td>
...
...
@@ -136,6 +144,28 @@
</span>
</td>
</tr>
<tr
class=
"no-borders-tr"
>
<td>
{% trans 'Update otp' %}:
</td>
<td>
<span
class=
"pull-right"
>
<a
type=
"button"
class=
"btn btn-primary btn-xs"
style=
"width: 54px"
id=
""
href=
"
{% if request.user.otp_enabled and request.user.otp_secret_key %}
{% if request.user.otp_force_enabled %}
javascript:void(0)
"
><span
style=
"color:#ed5565"
>
{% trans 'Disable' %}
</span>
{% else %}
{% url 'users:user-otp-disable-authentication' %}
">{% trans 'Disable' %}
{% endif %}
{% else %}
{% url 'users:user-otp-enable-authentication' %}
">{% trans 'Enable' %}
{% endif %}
</a>
</span>
</td>
</tr>
<tr>
<td>
{% trans 'Update SSH public key' %}:
</td>
<td>
...
...
apps/users/urls/views_urls.py
View file @
0bbfc743
...
...
@@ -10,6 +10,7 @@ urlpatterns = [
# Login view
url
(
r'^login$'
,
views
.
UserLoginView
.
as_view
(),
name
=
'login'
),
url
(
r'^logout$'
,
views
.
UserLogoutView
.
as_view
(),
name
=
'logout'
),
url
(
r'^login/otp$'
,
views
.
UserLoginOtpView
.
as_view
(),
name
=
'login-otp'
),
url
(
r'^password/forgot$'
,
views
.
UserForgotPasswordView
.
as_view
(),
name
=
'forgot-password'
),
url
(
r'^password/forgot/sendmail-success$'
,
views
.
UserForgotPasswordSendmailSuccessView
.
as_view
(),
name
=
'forgot-password-sendmail-success'
),
url
(
r'^password/reset$'
,
views
.
UserResetPasswordView
.
as_view
(),
name
=
'reset-password'
),
...
...
@@ -21,6 +22,11 @@ urlpatterns = [
url
(
r'^profile/password/update/$'
,
views
.
UserPasswordUpdateView
.
as_view
(),
name
=
'user-password-update'
),
url
(
r'^profile/pubkey/update/$'
,
views
.
UserPublicKeyUpdateView
.
as_view
(),
name
=
'user-pubkey-update'
),
url
(
r'^profile/pubkey/generate/$'
,
views
.
UserPublicKeyGenerateView
.
as_view
(),
name
=
'user-pubkey-generate'
),
url
(
r'^profile/otp/enable/authentication/$'
,
views
.
UserOtpEnableAuthenticationView
.
as_view
(),
name
=
'user-otp-enable-authentication'
),
url
(
r'^profile/otp/enable/install-app/$'
,
views
.
UserOtpEnableInstallAppView
.
as_view
(),
name
=
'user-otp-enable-install-app'
),
url
(
r'^profile/otp/enable/bind/$'
,
views
.
UserOtpEnableBindView
.
as_view
(),
name
=
'user-otp-enable-bind'
),
url
(
r'^profile/otp/disable/authentication/$'
,
views
.
UserOtpDisableAuthenticationView
.
as_view
(),
name
=
'user-otp-disable-authentication'
),
url
(
r'^profile/otp/settings-success/$'
,
views
.
UserOtpSettingsSuccessView
.
as_view
(),
name
=
'user-otp-settings-success'
),
# User view
url
(
r'^user$'
,
views
.
UserListView
.
as_view
(),
name
=
'user-list'
),
...
...
@@ -34,7 +40,6 @@ urlpatterns = [
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/assets'
,
views
.
UserGrantedAssetView
.
as_view
(),
name
=
'user-granted-asset'
),
url
(
r'^user/(?P<pk>[0-9a-zA-Z\-]{36})/login-history'
,
views
.
UserDetailView
.
as_view
(),
name
=
'user-login-history'
),
# User group view
url
(
r'^user-group$'
,
views
.
UserGroupListView
.
as_view
(),
name
=
'user-group-list'
),
url
(
r'^user-group/(?P<pk>[0-9a-zA-Z\-]{36})$'
,
views
.
UserGroupDetailView
.
as_view
(),
name
=
'user-group-detail'
),
...
...
apps/users/utils.py
View file @
0bbfc743
# ~*~ coding: utf-8 ~*~
#
from
__future__
import
unicode_literals
import
os
import
pyotp
import
base64
import
logging
import
uuid
...
...
@@ -17,7 +19,6 @@ from common.tasks import send_mail_async
from
common.utils
import
reverse
,
get_object_or_none
from
.models
import
User
,
LoginLog
logger
=
logging
.
getLogger
(
'jumpserver'
)
...
...
@@ -163,7 +164,7 @@ def generate_token(request, user):
remote_addr
=
request
.
META
.
get
(
'REMOTE_ADDR'
,
''
)
if
not
isinstance
(
remote_addr
,
bytes
):
remote_addr
=
remote_addr
.
encode
(
"utf-8"
)
remote_addr
=
base64
.
b16encode
(
remote_addr
)
#
.replace(b'=', '')
remote_addr
=
base64
.
b16encode
(
remote_addr
)
#
.replace(b'=', '')
token
=
cache
.
get
(
'
%
s_
%
s'
%
(
user
.
id
,
remote_addr
))
if
not
token
:
token
=
uuid
.
uuid4
()
.
hex
...
...
@@ -181,6 +182,16 @@ def validate_ip(ip):
return
False
def
get_login_ip
(
request
):
x_forwarded_for
=
request
.
META
.
get
(
'HTTP_X_FORWARDED_FOR'
,
''
)
.
split
(
','
)
if
x_forwarded_for
and
x_forwarded_for
[
0
]:
login_ip
=
x_forwarded_for
[
0
]
else
:
login_ip
=
request
.
META
.
get
(
'REMOTE_ADDR'
,
''
)
return
login_ip
def
write_login_log
(
username
,
type
=
''
,
ip
=
''
,
user_agent
=
''
):
if
not
(
ip
and
validate_ip
(
ip
)):
ip
=
ip
[:
15
]
...
...
@@ -211,3 +222,35 @@ def get_ip_city(ip, timeout=10):
except
ValueError
:
pass
return
city
def
get_user
(
request
):
if
is_login
(
request
):
user
=
request
.
user
else
:
user
=
cache
.
get
(
request
.
session
.
session_key
)
return
user
def
is_login
(
request
):
return
isinstance
(
request
.
user
,
User
)
def
redirect_user_first_login_or_index
(
request
,
redirect_field_name
):
if
request
.
user
.
is_first_login
:
return
reverse
(
'users:user-first-login'
)
return
request
.
POST
.
get
(
redirect_field_name
,
request
.
GET
.
get
(
redirect_field_name
,
reverse
(
'index'
)))
def
generate_otp_uri
(
user
,
issuer
=
"Jumpserver"
):
otp_secret_key
=
base64
.
b32encode
(
os
.
urandom
(
10
))
.
decode
(
'utf-8'
)
cache
.
set
(
'otp_secret_key'
,
otp_secret_key
,
300
)
totp
=
pyotp
.
TOTP
(
otp_secret_key
)
return
totp
.
provisioning_uri
(
name
=
user
.
username
,
issuer_name
=
issuer
)
def
check_otp_code
(
otp_secret_key
,
otp_code
):
totp
=
pyotp
.
TOTP
(
otp_secret_key
)
return
totp
.
verify
(
otp_code
)
apps/users/views/login.py
View file @
0bbfc743
...
...
@@ -19,17 +19,18 @@ from django.views.generic.base import TemplateView
from
django.views.generic.edit
import
FormView
from
formtools.wizard.views
import
SessionWizardView
from
django.conf
import
settings
from
django.core.cache
import
cache
from
common.utils
import
get_object_or_none
from
common.mixins
import
DatetimeSearchMixin
,
AdminUserRequiredMixin
from
..models
import
User
,
LoginLog
from
..utils
import
send_reset_password_mail
from
..utils
import
send_reset_password_mail
,
check_otp_code
,
get_login_ip
,
redirect_user_first_login_or_index
from
..tasks
import
write_login_log_async
from
..
import
forms
__all__
=
[
'UserLoginView'
,
'UserLogoutView'
,
'UserLoginView'
,
'UserLog
inOtpView'
,
'UserLog
outView'
,
'UserForgotPasswordView'
,
'UserForgotPasswordSendmailSuccessView'
,
'UserResetPasswordView'
,
'UserResetPasswordSuccessView'
,
'UserFirstLoginView'
,
'LoginLogListView'
...
...
@@ -53,27 +54,23 @@ class UserLoginView(FormView):
def
form_valid
(
self
,
form
):
if
not
self
.
request
.
session
.
test_cookie_worked
():
return
HttpResponse
(
_
(
"Please enable cookies and try again."
))
auth_login
(
self
.
request
,
form
.
get_user
())
x_forwarded_for
=
self
.
request
.
META
.
get
(
'HTTP_X_FORWARDED_FOR'
,
''
)
.
split
(
','
)
if
x_forwarded_for
and
x_forwarded_for
[
0
]:
login_ip
=
x_forwarded_for
[
0
]
else
:
login_ip
=
self
.
request
.
META
.
get
(
'REMOTE_ADDR'
,
''
)
user_agent
=
self
.
request
.
META
.
get
(
'HTTP_USER_AGENT'
,
''
)
write_login_log_async
.
delay
(
self
.
request
.
user
.
username
,
type
=
'W'
,
ip
=
login_ip
,
user_agent
=
user_agent
)
cache
.
set
(
self
.
request
.
session
.
session_key
,
form
.
get_user
(),
600
)
return
redirect
(
self
.
get_success_url
())
def
get_success_url
(
self
):
if
self
.
request
.
user
.
is_first_login
:
return
reverse
(
'users:user-first-login'
)
return
self
.
request
.
POST
.
get
(
self
.
redirect_field_name
,
self
.
request
.
GET
.
get
(
self
.
redirect_field_name
,
reverse
(
'index'
)))
user
=
cache
.
get
(
self
.
request
.
session
.
session_key
)
if
user
.
otp_enabled
and
user
.
otp_secret_key
:
# 1,2 & T
return
reverse
(
'users:login-otp'
)
elif
user
.
otp_enabled
and
not
user
.
otp_secret_key
:
# 1,2 & F
return
reverse
(
'users:user-otp-enable-authentication'
)
elif
not
user
.
otp_enabled
:
# 0 & T,F
auth_login
(
self
.
request
,
user
)
self
.
write_login_log
()
return
redirect_user_first_login_or_index
(
self
.
request
,
self
.
redirect_field_name
)
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
...
...
@@ -82,6 +79,44 @@ class UserLoginView(FormView):
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
write_login_log
(
self
):
login_ip
=
get_login_ip
(
self
.
request
)
user_agent
=
self
.
request
.
META
.
get
(
'HTTP_USER_AGENT'
,
''
)
write_login_log_async
.
delay
(
self
.
request
.
user
.
username
,
type
=
'W'
,
ip
=
login_ip
,
user_agent
=
user_agent
)
class
UserLoginOtpView
(
FormView
):
template_name
=
'users/login_otp.html'
form_class
=
forms
.
UserCheckOtpCodeForm
redirect_field_name
=
'next'
def
form_valid
(
self
,
form
):
user
=
cache
.
get
(
self
.
request
.
session
.
session_key
)
otp_code
=
form
.
cleaned_data
.
get
(
'otp_code'
)
otp_secret_key
=
user
.
otp_secret_key
if
check_otp_code
(
otp_secret_key
,
otp_code
):
auth_login
(
self
.
request
,
user
)
self
.
write_login_log
()
return
redirect
(
self
.
get_success_url
())
else
:
form
.
add_error
(
'otp_code'
,
_
(
'Otp code invalid'
))
return
super
()
.
form_invalid
(
form
)
def
get_success_url
(
self
):
return
redirect_user_first_login_or_index
(
self
.
request
,
self
.
redirect_field_name
)
def
write_login_log
(
self
):
login_ip
=
get_login_ip
(
self
.
request
)
user_agent
=
self
.
request
.
META
.
get
(
'HTTP_USER_AGENT'
,
''
)
write_login_log_async
.
delay
(
self
.
request
.
user
.
username
,
type
=
'W'
,
ip
=
login_ip
,
user_agent
=
user_agent
)
@method_decorator
(
never_cache
,
name
=
'dispatch'
)
class
UserLogoutView
(
TemplateView
):
...
...
apps/users/views/user.py
View file @
0bbfc743
...
...
@@ -11,6 +11,7 @@ from io import StringIO
from
django.contrib
import
messages
from
django.contrib.auth.mixins
import
LoginRequiredMixin
from
django.contrib.auth
import
authenticate
,
login
as
auth_login
from
django.contrib.messages.views
import
SuccessMessageMixin
from
django.core.cache
import
cache
from
django.http
import
HttpResponse
,
JsonResponse
...
...
@@ -34,9 +35,9 @@ from common.mixins import JSONResponseMixin
from
common.utils
import
get_logger
,
get_object_or_none
,
is_uuid
,
ssh_key_gen
from
..
import
forms
from
..models
import
User
,
UserGroup
from
..utils
import
AdminUserRequiredMixin
from
..utils
import
AdminUserRequiredMixin
,
generate_otp_uri
,
check_otp_code
,
get_user
,
is_login
from
..signals
import
post_user_create
from
..tasks
import
write_login_log_async
__all__
=
[
'UserListView'
,
'UserCreateView'
,
'UserDetailView'
,
...
...
@@ -46,6 +47,9 @@ __all__ = [
'UserProfileUpdateView'
,
'UserPasswordUpdateView'
,
'UserPublicKeyUpdateView'
,
'UserBulkUpdateView'
,
'UserPublicKeyGenerateView'
,
'UserOtpEnableAuthenticationView'
,
'UserOtpEnableInstallAppView'
,
'UserOtpEnableBindView'
,
'UserOtpSettingsSuccessView'
,
'UserOtpDisableAuthenticationView'
,
]
logger
=
get_logger
(
__name__
)
...
...
@@ -380,6 +384,7 @@ class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView):
class
UserPublicKeyGenerateView
(
LoginRequiredMixin
,
View
):
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
private
,
public
=
ssh_key_gen
(
username
=
request
.
user
.
username
,
hostname
=
'jumpserver'
)
request
.
user
.
public_key
=
public
...
...
@@ -389,3 +394,125 @@ class UserPublicKeyGenerateView(LoginRequiredMixin, View):
response
[
'Content-Disposition'
]
=
'attachment; filename={}'
.
format
(
filename
)
return
response
class
UserOtpEnableAuthenticationView
(
FormView
):
template_name
=
'users/user_password_authentication.html'
form_class
=
forms
.
UserCheckPasswordForm
def
get_form
(
self
,
form_class
=
None
):
form
=
super
()
.
get_form
(
form_class
=
form_class
)
form
[
'username'
]
.
initial
=
get_user
(
self
.
request
)
.
username
return
form
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'user'
:
get_user
(
self
.
request
)
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
form_valid
(
self
,
form
):
password
=
form
.
cleaned_data
.
get
(
'password'
)
user
=
get_user
(
self
.
request
)
user
=
authenticate
(
username
=
user
.
username
,
password
=
password
)
if
not
user
:
form
.
add_error
(
"password"
,
_
(
"Password invalid"
))
return
self
.
form_invalid
(
form
)
return
redirect
(
self
.
get_success_url
())
def
get_success_url
(
self
):
return
reverse
(
'users:user-otp-enable-install-app'
)
class
UserOtpEnableInstallAppView
(
TemplateView
):
template_name
=
'users/user_otp_enable_install_app.html'
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'user'
:
get_user
(
self
.
request
)
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
class
UserOtpEnableBindView
(
TemplateView
,
FormView
):
template_name
=
'users/user_otp_enable_bind.html'
form_class
=
forms
.
UserCheckOtpCodeForm
success_url
=
reverse_lazy
(
'users:user-otp-settings-success'
)
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'otp_uri'
:
generate_otp_uri
(
user
=
get_user
(
self
.
request
)),
'user'
:
get_user
(
self
.
request
)
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
form_valid
(
self
,
form
):
otp_code
=
form
.
cleaned_data
.
get
(
'otp_code'
)
otp_secret_key
=
cache
.
get
(
'otp_secret_key'
)
if
check_otp_code
(
otp_secret_key
,
otp_code
):
self
.
save_otp
(
otp_secret_key
)
return
super
()
.
form_valid
(
form
)
else
:
form
.
add_error
(
"otp_code"
,
_
(
"Otp code invalid"
))
return
self
.
form_invalid
(
form
)
def
save_otp
(
self
,
otp_secret_key
):
user
=
get_user
(
self
.
request
)
user
.
enable_otp
()
user
.
otp_secret_key
=
otp_secret_key
user
.
save
()
class
UserOtpDisableAuthenticationView
(
FormView
):
template_name
=
'users/user_otp_authentication.html'
form_class
=
forms
.
UserCheckOtpCodeForm
success_url
=
reverse_lazy
(
'users:user-otp-settings-success'
)
def
form_valid
(
self
,
form
):
user
=
self
.
request
.
user
otp_code
=
form
.
cleaned_data
.
get
(
'otp_code'
)
otp_secret_key
=
user
.
otp_secret_key
if
check_otp_code
(
otp_secret_key
,
otp_code
):
user
.
disable_otp
()
user
.
save
()
return
super
()
.
form_valid
(
form
)
else
:
form
.
add_error
(
'otp_code'
,
_
(
'Otp code invalid'
))
return
super
()
.
form_invalid
(
form
)
class
UserOtpSettingsSuccessView
(
TemplateView
):
template_name
=
'flash_message_standalone.html'
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
response
=
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
if
is_login
(
request
):
auth_logout
(
request
)
return
response
def
get_context_data
(
self
,
**
kwargs
):
title
,
describe
=
self
.
get_title_describe
()
context
=
{
'title'
:
title
,
'messages'
:
describe
,
'interval'
:
1
,
'redirect_url'
:
reverse
(
'users:login'
),
'auto_redirect'
:
True
,
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
get_title_describe
(
self
):
user
=
get_user
(
self
.
request
)
title
=
_
(
'OTP enable success'
)
describe
=
_
(
'OTP enable success, return login page'
)
if
not
user
.
otp_enabled
:
title
=
_
(
'OTP disable success'
)
describe
=
_
(
'OTP disable success, return login page'
)
return
title
,
describe
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment