authtoken.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package clientman
  15. import (
  16. "bytes"
  17. "compress/flate"
  18. "context"
  19. "crypto/rsa"
  20. "encoding/base64"
  21. "encoding/binary"
  22. "io"
  23. "math/rand"
  24. "time"
  25. "github.com/lestrrat-go/jwx/jwa"
  26. "github.com/lestrrat-go/jwx/jwe"
  27. "github.com/lestrrat-go/jwx/jwt"
  28. "github.com/pquerna/otp/totp"
  29. "yunion.io/x/jsonutils"
  30. "yunion.io/x/pkg/errors"
  31. "yunion.io/x/onecloud/pkg/apigateway/options"
  32. "yunion.io/x/onecloud/pkg/httperrors"
  33. "yunion.io/x/onecloud/pkg/mcclient"
  34. "yunion.io/x/onecloud/pkg/mcclient/auth"
  35. )
  36. const (
  37. TotpEnable = '1'
  38. TotpDisable = '0'
  39. )
  40. var (
  41. privateKey *rsa.PrivateKey
  42. )
  43. func setPrivateKey(key *rsa.PrivateKey) {
  44. privateKey = key
  45. }
  46. type SAuthToken struct {
  47. token string
  48. verifyTotp bool
  49. enableTotp bool
  50. initTotp bool
  51. isSsoLogin bool
  52. retryCount int // 重试计数器
  53. lockExpireTime uint32 // 锁定时间
  54. }
  55. func (t SAuthToken) encodeBytes() []byte {
  56. msg := bytes.Buffer{}
  57. if t.verifyTotp {
  58. msg.WriteByte(TotpEnable)
  59. } else {
  60. msg.WriteByte(TotpDisable)
  61. }
  62. if t.enableTotp {
  63. msg.WriteByte(TotpEnable)
  64. } else {
  65. msg.WriteByte(TotpDisable)
  66. }
  67. if t.initTotp {
  68. msg.WriteByte(TotpEnable)
  69. } else {
  70. msg.WriteByte(TotpDisable)
  71. }
  72. if t.isSsoLogin {
  73. msg.WriteByte(TotpEnable)
  74. } else {
  75. msg.WriteByte(TotpDisable)
  76. }
  77. msg.WriteByte(byte(rand.Int()))
  78. msg.WriteByte(byte(t.retryCount))
  79. expBytes := make([]byte, 4)
  80. binary.LittleEndian.PutUint32(expBytes, t.lockExpireTime)
  81. msg.Write(expBytes)
  82. msg.WriteString(t.token)
  83. return msg.Bytes()
  84. }
  85. func (t SAuthToken) Encode() string {
  86. encBytes := t.encodeBytes()
  87. if privateKey != nil {
  88. return EncryptString(encBytes)
  89. } else {
  90. return compressString(encBytes)
  91. }
  92. }
  93. func Decode(t string) (*SAuthToken, error) {
  94. var tBytes []byte
  95. var err error
  96. if privateKey != nil {
  97. tBytes, err = DecryptString(t)
  98. if err != nil {
  99. return nil, errors.Wrap(err, "decryptString")
  100. }
  101. } else {
  102. tBytes, err = decompressString(t)
  103. if err != nil {
  104. return nil, errors.Wrap(err, "decompressString")
  105. }
  106. }
  107. return decodeBytes(tBytes)
  108. }
  109. func decodeBytes(tt []byte) (*SAuthToken, error) {
  110. ret := SAuthToken{}
  111. if len(tt) < 10 {
  112. return nil, errors.Wrap(errors.ErrInvalidStatus, "too short")
  113. }
  114. if tt[0] == TotpEnable {
  115. ret.verifyTotp = true
  116. } else {
  117. ret.verifyTotp = false
  118. }
  119. if tt[1] == TotpEnable {
  120. ret.enableTotp = true
  121. } else {
  122. ret.enableTotp = false
  123. }
  124. if tt[2] == TotpEnable {
  125. ret.initTotp = true
  126. } else {
  127. ret.initTotp = false
  128. }
  129. if tt[3] == TotpEnable {
  130. ret.isSsoLogin = true
  131. } else {
  132. ret.isSsoLogin = false
  133. }
  134. // 4: skip rand number
  135. ret.retryCount = int(tt[5])
  136. ret.lockExpireTime = binary.LittleEndian.Uint32(tt[6:])
  137. ret.token = string(tt[10:])
  138. return &ret, nil
  139. }
  140. func compressString(in []byte) string {
  141. buf := new(bytes.Buffer)
  142. compressor, _ := flate.NewWriter(buf, 9)
  143. compressor.Write(in)
  144. compressor.Close()
  145. return base64.URLEncoding.EncodeToString(buf.Bytes())
  146. }
  147. func EncryptString(in []byte) string {
  148. enc, _ := jwe.Encrypt(in, jwa.RSA1_5, &privateKey.PublicKey, jwa.A128GCM, jwa.Deflate)
  149. return string(enc)
  150. }
  151. func decompressString(in string) ([]byte, error) {
  152. inBytes, err := base64.URLEncoding.DecodeString(in)
  153. if err != nil {
  154. return nil, errors.Wrap(err, "base64.URLEncoding.DecodeString")
  155. }
  156. buf := new(bytes.Buffer)
  157. decompressor := flate.NewReader(bytes.NewReader(inBytes))
  158. _, err = io.Copy(buf, decompressor)
  159. if err != nil {
  160. return nil, errors.Wrap(err, "decompress")
  161. }
  162. decompressor.Close()
  163. return buf.Bytes(), nil
  164. }
  165. func DecryptString(in string) ([]byte, error) {
  166. return jwe.Decrypt([]byte(in), jwa.RSA1_5, privateKey)
  167. }
  168. func (t SAuthToken) GetToken(ctx context.Context) (mcclient.TokenCredential, error) {
  169. return auth.Verify(ctx, t.token)
  170. }
  171. func (t SAuthToken) GetAuthCookie(token mcclient.TokenCredential) string {
  172. sid := t.Encode()
  173. info := jsonutils.NewDict()
  174. info.Add(jsonutils.NewTimeString(token.GetExpires()), "exp")
  175. info.Add(jsonutils.NewString(sid), "session")
  176. info.Add(jsonutils.NewBool(t.verifyTotp), "totp_verified") // 用户totp验证通过
  177. info.Add(jsonutils.NewBool(t.initTotp), "totp_init") // 是否初始化TOTP密钥
  178. info.Add(jsonutils.NewBool(t.enableTotp), "totp_on") // 用户totp 开启状态。 True(已开启)|False(未开启)
  179. info.Add(jsonutils.NewBool(t.isSsoLogin), "is_sso") // 用户是否通过SSO登录
  180. info.Add(jsonutils.NewBool(options.Options.EnableTotp), "system_totp_on") // 全局totp 开启状态。 True(已开启)|False(未开启)
  181. info.Add(jsonutils.NewString(token.GetUserId()), "user_id")
  182. info.Add(jsonutils.NewString(token.GetUserName()), "user")
  183. return info.String()
  184. }
  185. func (t SAuthToken) IsTotpVerified() bool {
  186. if !options.Options.EnableTotp {
  187. return true
  188. }
  189. if !t.enableTotp {
  190. return true
  191. }
  192. return t.verifyTotp
  193. }
  194. func (t SAuthToken) IsTotpEnabled() bool {
  195. return t.enableTotp
  196. }
  197. func (t SAuthToken) IsTotpInitialized() bool {
  198. return t.initTotp
  199. }
  200. func (t *SAuthToken) SetTotpInitialized() {
  201. t.initTotp = true
  202. }
  203. func (t *SAuthToken) SetToken(tid string) {
  204. t.token = tid
  205. }
  206. func NewAuthToken(tid string, enableTotp bool, isTotpInit bool, isSsoLogin bool) *SAuthToken {
  207. return &SAuthToken{
  208. token: tid,
  209. enableTotp: enableTotp,
  210. initTotp: isTotpInit,
  211. isSsoLogin: isSsoLogin,
  212. verifyTotp: false,
  213. }
  214. }
  215. func (t *SAuthToken) updateRetryCount() {
  216. if t.retryCount < MAX_OTP_RETRY {
  217. t.retryCount += 1
  218. // 锁定
  219. if t.retryCount >= MAX_OTP_RETRY {
  220. t.lockExpireTime = uint32(time.Now().Add(30 * time.Second).Unix())
  221. }
  222. } else {
  223. // 清零计数器,解除锁定
  224. if t.lockExpireTime < uint32(time.Now().Unix()) {
  225. t.lockExpireTime = 0
  226. t.retryCount = 0
  227. }
  228. }
  229. }
  230. func (t *SAuthToken) VerifyTotpPasscode(s *mcclient.ClientSession, uid, passcode string) error {
  231. if t.lockExpireTime > uint32(time.Now().Unix()) {
  232. return errors.Wrapf(httperrors.ErrResourceBusy, "locked, retry after %d seconds", t.lockExpireTime-uint32(time.Now().Unix()))
  233. }
  234. secret, err := fetchUserTotpCredSecret(s, uid)
  235. if err != nil {
  236. return errors.Wrap(err, "fetch totp secrets error")
  237. }
  238. if totp.Validate(passcode, secret) {
  239. t.verifyTotp = true
  240. t.lockExpireTime = 0
  241. t.retryCount = 0
  242. return nil
  243. }
  244. t.updateRetryCount()
  245. return errors.Wrap(httperrors.ErrInputParameter, "invalid passcode")
  246. }
  247. func SignJWT(t jwt.Token) (string, error) {
  248. //jwkKey, err := jwk.New(privateKey)
  249. //if err != nil {
  250. // return "", errors.Wrap(err, "jwk.New")
  251. //}
  252. signed, err := jwt.Sign(t, jwa.RS256, privateKey)
  253. if err != nil {
  254. return "", errors.Wrap(err, "jwt.Sign")
  255. }
  256. return string(signed), nil
  257. }
  258. func GetJWKs(ctx context.Context) (jsonutils.JSONObject, error) {
  259. key := jsonutils.NewDict()
  260. key.Set("use", jsonutils.NewString("sig"))
  261. key.Set("kty", jsonutils.NewString("RSA"))
  262. key.Set("alg", jsonutils.NewString("RS256"))
  263. key.Set("e", jsonutils.NewString("AQAB"))
  264. key.Set("n", jsonutils.NewString(base64.URLEncoding.EncodeToString(privateKey.PublicKey.N.Bytes())))
  265. ret := jsonutils.NewDict()
  266. ret.Set("keys", jsonutils.NewArray(key))
  267. return ret, nil
  268. }