github.com/infraboard/keyauth@v0.8.1/apps/token/issuer/issuer.go (about)

     1  package issuer
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"regexp"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/infraboard/mcube/app"
    12  	"github.com/infraboard/mcube/exception"
    13  	"github.com/infraboard/mcube/http/request"
    14  	"github.com/infraboard/mcube/logger"
    15  	"github.com/infraboard/mcube/logger/zap"
    16  	"github.com/rs/xid"
    17  
    18  	wechatWork "github.com/infraboard/keyauth/apps/wxwork"
    19  
    20  	"github.com/infraboard/keyauth/apps/application"
    21  	"github.com/infraboard/keyauth/apps/domain"
    22  	"github.com/infraboard/keyauth/apps/provider"
    23  	"github.com/infraboard/keyauth/apps/provider/auth/ldap"
    24  	"github.com/infraboard/keyauth/apps/provider/auth/wxwork"
    25  	"github.com/infraboard/keyauth/apps/token"
    26  	"github.com/infraboard/keyauth/apps/user"
    27  	"github.com/infraboard/keyauth/apps/user/types"
    28  	"github.com/infraboard/keyauth/common/password"
    29  )
    30  
    31  // NewTokenIssuer todo
    32  func NewTokenIssuer() (Issuer, error) {
    33  	issuer := &issuer{
    34  		user:       app.GetGrpcApp(user.AppName).(user.ServiceServer),
    35  		domain:     app.GetGrpcApp(domain.AppName).(domain.ServiceServer),
    36  		token:      app.GetGrpcApp(token.AppName).(token.ServiceServer),
    37  		ldap:       app.GetInternalApp(provider.AppName).(provider.LDAP),
    38  		wechatWork: app.GetInternalApp(wechatWork.AppName).(wechatWork.WechatWork),
    39  		app:        app.GetGrpcApp(application.AppName).(application.ServiceServer),
    40  		emailRE:    regexp.MustCompile(`([a-zA-Z0-9]+)@([a-zA-Z0-9\.]+)\.([a-zA-Z0-9]+)`),
    41  		log:        zap.L().Named("Token Issuer"),
    42  	}
    43  	return issuer, nil
    44  }
    45  
    46  // TokenIssuer 基于该数据进行扩展
    47  type issuer struct {
    48  	app        application.ServiceServer
    49  	token      token.ServiceServer
    50  	user       user.ServiceServer
    51  	domain     domain.ServiceServer
    52  	ldap       provider.LDAP
    53  	wechatWork wechatWork.WechatWork
    54  	emailRE    *regexp.Regexp
    55  	log        logger.Logger
    56  }
    57  
    58  func (i *issuer) checkUserPass(ctx context.Context, user, pass string) (*user.User, error) {
    59  	u, err := i.getUser(ctx, user)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	if err := u.HashedPassword.CheckPassword(pass); err != nil {
    65  		return nil, err
    66  	}
    67  	return u, nil
    68  }
    69  
    70  func (i *issuer) checkUserPassExpired(ctx context.Context, u *user.User) error {
    71  	d, err := i.getDomain(ctx, u)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	// 检测密码是否过期
    77  	err = d.SecuritySetting.PasswordSecurity.IsPasswordExpired(u.HashedPassword)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	return nil
    83  }
    84  
    85  func (i *issuer) getUser(ctx context.Context, name string) (*user.User, error) {
    86  	req := user.NewDescriptAccountRequest()
    87  	req.Account = name
    88  	return i.user.DescribeAccount(ctx, req)
    89  }
    90  
    91  func (i *issuer) getDomain(ctx context.Context, u *user.User) (*domain.Domain, error) {
    92  	req := domain.NewDescribeDomainRequestWithName(u.Domain)
    93  	return i.domain.DescribeDomain(ctx, req)
    94  }
    95  
    96  func (i *issuer) setTokenDomain(ctx context.Context, tk *token.Token) error {
    97  	// 获取最近1个
    98  	req := domain.NewQueryDomainRequest(request.NewPageRequest(1, 1))
    99  	domains, err := i.domain.QueryDomain(ctx, req)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	if domains.Length() > 0 {
   105  		tk.Domain = domains.Items[0].Name
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  // IssueToken 颁发token
   112  func (i *issuer) IssueToken(ctx context.Context, req *token.IssueTokenRequest) (*token.Token, error) {
   113  	if err := req.Validate(); err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	// 校验应用端的合法性
   118  	app, err := i.CheckClient(ctx, req.ClientId, req.ClientSecret)
   119  	if err != nil {
   120  		return nil, exception.NewUnauthorized(err.Error())
   121  	}
   122  
   123  	// 校验用户的身份
   124  	switch req.GrantType {
   125  	case token.GrantType_PASSWORD:
   126  		// 判断用户的密码是否正确
   127  		u, checkErr := i.checkUserPass(ctx, req.Username, req.Password)
   128  		if checkErr != nil {
   129  			i.log.Debugf("issue password token error, %s", checkErr)
   130  			return nil, exception.NewUnauthorized("user or password not connrect")
   131  		}
   132  
   133  		// 校验用的密码是否过期
   134  		if err := i.checkUserPassExpired(ctx, u); err != nil {
   135  			i.log.Debugf("issue password token error, %s", err)
   136  			if v, ok := err.(exception.APIException); ok {
   137  				v.WithData(u.Account)
   138  			}
   139  			return nil, err
   140  		}
   141  
   142  		// 颁发给用户一个token
   143  		tk := i.issueUserToken(app, u, token.GrantType_PASSWORD)
   144  		switch u.Type {
   145  		case types.UserType_SUPPER, types.UserType_PRIMARY:
   146  			err := i.setTokenDomain(ctx, tk)
   147  			if err != nil {
   148  				return nil, fmt.Errorf("set token domain error, %s", err)
   149  			}
   150  			tk.Domain = u.Domain
   151  		default:
   152  			tk.Domain = u.Domain
   153  		}
   154  
   155  		return tk, nil
   156  	case token.GrantType_REFRESH:
   157  		validateReq := token.NewValidateTokenRequest()
   158  		validateReq.RefreshToken = req.RefreshToken
   159  		tk, err := i.token.ValidateToken(context.Background(), validateReq)
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  		if tk.AccessToken != req.AccessToken {
   164  			return nil, exception.NewPermissionDeny("refresh_token's access_tken not connrect")
   165  		}
   166  
   167  		u, err := i.getUser(ctx, tk.Account)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  		newTK := i.issueUserToken(app, u, token.GrantType_REFRESH)
   172  		newTK.Domain = tk.Domain
   173  		newTK.StartGrantType = tk.GetStartGrantType()
   174  		newTK.SessionId = tk.SessionId
   175  		newTK.NamespaceId = tk.NamespaceId
   176  
   177  		revolkReq := token.NewRevolkTokenRequest(app.ClientId, app.ClientSecret)
   178  		revolkReq.AccessToken = req.AccessToken
   179  		revolkReq.LogoutSession = false
   180  
   181  		if _, err := i.token.RevolkToken(ctx, revolkReq); err != nil {
   182  			return nil, err
   183  		}
   184  		return newTK, nil
   185  	case token.GrantType_ACCESS:
   186  		validateReq := token.NewValidateTokenRequest()
   187  		validateReq.AccessToken = req.AccessToken
   188  		tk, err := i.token.ValidateToken(context.Background(), validateReq)
   189  		if err != nil {
   190  			return nil, exception.NewUnauthorized(err.Error())
   191  		}
   192  
   193  		u, err := i.getUser(ctx, tk.Account)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  		newTK := i.issueUserToken(app, u, token.GrantType_ACCESS)
   198  		newTK.Domain = tk.Domain
   199  		newTK.AccessExpiredAt = req.AccessExpiredAt
   200  		newTK.RefreshExpiredAt = 4 * req.AccessExpiredAt
   201  		newTK.Description = req.Description
   202  		return newTK, nil
   203  	case token.GrantType_LDAP:
   204  		userName, dn, err := i.genBaseDN(req.Username)
   205  		if err != nil {
   206  			return nil, err
   207  		}
   208  
   209  		descReq := provider.NewDescribeLDAPConfigWithBaseDN(dn)
   210  		ldapConf, err := i.ldap.DescribeConfig(descReq)
   211  		if err != nil {
   212  			return nil, err
   213  		}
   214  		pv := ldap.NewProvider(ldapConf.Config)
   215  		ok, err := pv.CheckUserPassword(userName, req.Password)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		if !ok {
   220  			return nil, exception.NewUnauthorized("用户名或者密码不对")
   221  		}
   222  		u, err := i.syncLDAPUser(ctx, req.Username)
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  		newTK := i.issueUserToken(app, u, token.GrantType_LDAP)
   227  		newTK.Domain = ldapConf.Domain
   228  		return newTK, nil
   229  	case token.GrantType_WECHAT_WORK:
   230  		ww, err := i.wechatWork.DescribeConfig(&wechatWork.DescribeWechatWorkConf{Domain: req.Username})
   231  		if err != nil {
   232  			return nil, err
   233  		}
   234  		if req.State != ww.State {
   235  			return nil, errors.New("State is error! ")
   236  		}
   237  		np := wxwork.NewAuth(ww.CorpID, ww.CorpSecret, ww.AgentID)
   238  		userID, err := np.CheckCallBack(&wxwork.ScanCodeRequest{
   239  			Code:    req.AuthCode,
   240  			State:   req.State,
   241  			AppID:   req.UserAgent,
   242  			Service: req.Service,
   243  		})
   244  		if err != nil {
   245  			return nil, err
   246  		}
   247  		wxUser := np.GetUserInfo(userID)
   248  		u, err := i.syncWXWORKUser(ctx, &user.User{
   249  			Account: wxUser.Name,
   250  			Domain:  ww.Domain,
   251  			Profile: &user.Profile{
   252  				RealName: wxUser.UserID,
   253  				NickName: wxUser.Name,
   254  				Avatar:   wxUser.Avatar,
   255  				Email:    wxUser.Email,
   256  				Phone:    wxUser.Mobile,
   257  			},
   258  		})
   259  		if err != nil {
   260  			return nil, err
   261  		}
   262  		newTK := i.issueUserToken(app, u, token.GrantType_WECHAT_WORK)
   263  		return newTK, nil
   264  
   265  	case token.GrantType_CLIENT:
   266  		return nil, exception.NewInternalServerError("not impl")
   267  	case token.GrantType_AUTH_CODE:
   268  		return nil, exception.NewInternalServerError("not impl")
   269  	default:
   270  		return nil, exception.NewInternalServerError("unknown grant type %s", req.GrantType)
   271  	}
   272  }
   273  
   274  func (i *issuer) genBaseDN(username string) (string, string, error) {
   275  	match := i.emailRE.FindAllStringSubmatch(username, -1)
   276  	if len(match) == 0 {
   277  		return "", "", exception.NewBadRequest("ldap user name must like username@company.com")
   278  	}
   279  
   280  	sub := match[0]
   281  	if len(sub) < 4 {
   282  		return "", "", exception.NewBadRequest("ldap user name must like username@company.com")
   283  	}
   284  
   285  	dns := []string{}
   286  	for _, dn := range sub[2:] {
   287  		dns = append(dns, "dc="+dn)
   288  	}
   289  
   290  	return sub[1], strings.Join(dns, ","), nil
   291  }
   292  
   293  func (i *issuer) syncLDAPUser(ctx context.Context, userName string) (*user.User, error) {
   294  	descUser := user.NewDescriptAccountRequestWithAccount(userName)
   295  	u, err := i.user.DescribeAccount(ctx, descUser)
   296  
   297  	if u != nil && u.Type.IsIn(types.UserType_PRIMARY, types.UserType_SUPPER) {
   298  		return nil, exception.NewBadRequest("用户名和主账号用户名冲突, 请修改")
   299  	}
   300  
   301  	if err != nil {
   302  		if exception.IsNotFoundError(err) {
   303  			req := user.NewCreateUserRequestWithLDAPSync(userName, i.randomPass())
   304  			req.UserType = types.UserType_SUB
   305  			u, err = i.user.CreateAccount(ctx, req)
   306  			if err != nil {
   307  				return nil, err
   308  			}
   309  		}
   310  		return u, err
   311  	}
   312  
   313  	return u, nil
   314  }
   315  
   316  func (i *issuer) syncWXWORKUser(ctx context.Context, reqUser *user.User) (*user.User, error) {
   317  	descUser := user.NewDescriptAccountRequestWithAccount(reqUser.Account)
   318  	u, err := i.user.DescribeAccount(ctx, descUser)
   319  
   320  	if u != nil && u.Type.IsIn(types.UserType_PRIMARY, types.UserType_SUPPER) {
   321  		return nil, exception.NewBadRequest("用户名和主账号用户名冲突, 请修改")
   322  	}
   323  
   324  	if err != nil {
   325  		if exception.IsNotFoundError(err) {
   326  			req := user.NewCreateUserRequestWithWXWORKSync(reqUser.Account, i.randomPass())
   327  			req.UserType = types.UserType_SUB
   328  			req.Profile = reqUser.Profile
   329  			req.Domain = reqUser.Domain
   330  			u, err = i.user.CreateAccount(ctx, req)
   331  			if err != nil {
   332  				return nil, err
   333  			}
   334  		}
   335  		return u, err
   336  	}
   337  
   338  	return u, nil
   339  }
   340  
   341  func (i *issuer) randomPass() string {
   342  	rpass, err := password.NewWithDefault().Generate()
   343  	if err != nil {
   344  		i.log.Warnf("generate random password error, %s, use uuid for random password", err)
   345  	}
   346  	if rpass != nil {
   347  		return *rpass
   348  	}
   349  
   350  	return xid.New().String()
   351  }
   352  
   353  func (i *issuer) issueUserToken(app *application.Application, u *user.User, gt token.GrantType) *token.Token {
   354  	tk := i.newBearToken(app, gt)
   355  	tk.Account = u.Account
   356  	tk.UserType = u.Type
   357  	return tk
   358  }
   359  
   360  func (i *issuer) newBearToken(app *application.Application, gt token.GrantType) *token.Token {
   361  	now := time.Now()
   362  	tk := &token.Token{
   363  		Type:            token.TokenType_BEARER,
   364  		AccessToken:     token.MakeBearer(24),
   365  		RefreshToken:    token.MakeBearer(32),
   366  		CreateAt:        now.UnixNano() / 1000000,
   367  		ClientId:        app.ClientId,
   368  		GrantType:       gt,
   369  		ApplicationId:   app.Id,
   370  		ApplicationName: app.Name,
   371  	}
   372  
   373  	if app.AccessTokenExpireSecond != 0 {
   374  		accessExpire := now.Add(time.Duration(app.AccessTokenExpireSecond) * time.Second)
   375  		tk.AccessExpiredAt = accessExpire.UnixNano() / 1000000
   376  	}
   377  
   378  	if app.RefreshTokenExpireSecond != 0 {
   379  		refreshExpir := now.Add(time.Duration(app.RefreshTokenExpireSecond) * time.Second)
   380  		tk.RefreshExpiredAt = refreshExpir.UnixNano() / 1000000
   381  	}
   382  
   383  	return tk
   384  }