blob: 8e55c618e6ad92296f07177927f764891e3ee1b1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package wecom
import (
"embed"
"fmt"
"net/http"
"sync"
"time"
"github.com/apache/answer-plugins/user-center-wecom/i18n"
"github.com/apache/answer-plugins/util"
"github.com/apache/answer/plugin"
"github.com/gin-gonic/gin"
"github.com/patrickmn/go-cache"
"github.com/segmentfault/pacman/log"
)
//go:embed info.yaml
var Info embed.FS
type UserCenter struct {
Config *UserCenterConfig
Company *Company
UserConfigCache *UserConfigCache
Cache *cache.Cache
syncLock sync.Mutex
syncing bool
syncSuccess bool
syncTime time.Time
}
func (uc *UserCenter) RegisterUnAuthRouter(r *gin.RouterGroup) {
r.GET("/wecom/login/url", uc.GetRedirectURL)
r.GET("/wecom/login/check", uc.CheckUserLogin)
}
func (uc *UserCenter) RegisterAuthUserRouter(r *gin.RouterGroup) {
}
func (uc *UserCenter) RegisterAuthAdminRouter(r *gin.RouterGroup) {
r.GET("/wecom/sync", uc.Sync)
r.GET("/wecom/data", uc.Data)
}
func (uc *UserCenter) AfterLogin(externalID, accessToken string) {
log.Debugf("user %s is login", externalID)
uc.Cache.Set(externalID, accessToken, time.Minute*5)
}
func (uc *UserCenter) UserStatus(externalID string) (userStatus plugin.UserStatus) {
if len(externalID) == 0 {
return plugin.UserStatusAvailable
}
var err error
userDetailInfo := uc.Company.UserDetailInfoMapping[externalID]
if userDetailInfo == nil {
userDetailInfo, err = uc.Company.GetUserDetailInfo(externalID)
if err != nil {
log.Errorf("get user detail info failed: %v", err)
}
}
if userDetailInfo == nil {
return plugin.UserStatusDeleted
}
switch userDetailInfo.Status {
case 1:
return plugin.UserStatusAvailable
case 2:
return plugin.UserStatusSuspended
default:
return plugin.UserStatusDeleted
}
}
func init() {
uc := &UserCenter{
Config: &UserCenterConfig{},
UserConfigCache: NewUserConfigCache(),
Company: NewCompany("", "", ""),
Cache: cache.New(5*time.Minute, 10*time.Minute),
syncLock: sync.Mutex{},
}
plugin.Register(uc)
uc.CronSyncData()
}
func (uc *UserCenter) Info() plugin.Info {
info := &util.Info{}
info.GetInfo(Info)
return plugin.Info{
Name: plugin.MakeTranslator(i18n.InfoName),
SlugName: info.SlugName,
Description: plugin.MakeTranslator(i18n.InfoDescription),
Author: info.Author,
Version: info.Version,
Link: info.Link,
}
}
func (uc *UserCenter) Description() plugin.UserCenterDesc {
redirectURL := "/user-center/auth"
desc := plugin.UserCenterDesc{
Name: "WeCom",
DisplayName: plugin.MakeTranslator(i18n.InfoName),
Icon: "PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNiAxNCIgZmlsbD0iY3VycmVudENvbG9yIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTkuMDY4NTkgMTAuOTkwMkM4Ljc1NDMxIDEwLjgzMjcgOC41OTQ2MiAxMC41NzQ0IDguNTczODYgMTAuMjI3NUM4LjU2NjE5IDEwLjA5OTggOC41MDkwMiAxMC4wODU0IDguMzk0NjggMTAuMTE4OUM3Ljg0NTY2IDEwLjI3OTIgNy4yODQ4MiAxMC4zNTUzIDYuNzExNTIgMTAuMzY0OEM2LjEyMTI5IDEwLjM3NDcgNS41NDA2NSAxMC4zMTE4IDQuOTc3MjUgMTAuMTQ0MUM0LjY1MTQ4IDEwLjA0NzEgNC4zODg5NSAxMC4xMjg1IDQuMTIwMDIgMTAuMjk5QzMuODc4NTcgMTAuNDUyMyAzLjYzMDExIDEwLjU5NDggMy4zODE3OSAxMC43MzczTDMuMzgxNzcgMTAuNzM3M0MzLjMxMTgxIDEwLjc3NzUgMy4yNDE4NyAxMC44MTc2IDMuMTcyMDkgMTAuODU4QzMuMTYzNjcgMTAuODYyOSAzLjE1NDk4IDEwLjg2ODcgMy4xNDYwOCAxMC44NzQ4TDMuMTQ2MDUgMTAuODc0OEMzLjEwOTA1IDEwLjg5OTggMy4wNjgzMyAxMC45Mjc0IDMuMDI3NDEgMTAuODk3M0MyLjk3OTU0IDEwLjg2MiAyLjk5NTE1IDEwLjgwOCAzLjAwOTQ1IDEwLjc1ODVDMy4wMTE4NCAxMC43NTAyIDMuMDE0MjEgMTAuNzQyIDMuMDE2MjMgMTAuNzM0QzMuMDM2MTEgMTAuNjU3NiAzLjA1NTEyIDEwLjU4MDggMy4wNzQxMiAxMC41MDRDMy4xMjI4OSAxMC4zMDY5IDMuMTcxNjcgMTAuMTA5OCAzLjIzNTMzIDkuOTE3N0MzLjM0MTY4IDkuNTk2NCAzLjI0OTA2IDkuMzYxNjUgMi45ODUyNSA5LjE2Nzc4QzIuNTc0ODQgOC44NjU5NiAyLjIyOTI2IDguNDk4MzUgMS45NDM0MSA4LjA3NzRDMS4yMTg3MiA3LjAwOTA2IDEuMDIwMzggNS44NDkwNSAxLjM5MDIzIDQuNjEwNDdDMS42NTA1MyAzLjczOTE4IDIuMTY3NjIgMy4wMjg4NyAyLjg3NjY1IDIuNDY2MTFDNC4wNzkxNCAxLjUxMTc5IDUuNDU5ODUgMS4xMjU5NyA2Ljk4Mzk2IDEuMjAwMzlDNy45ODcxNSAxLjI0OTI1IDguOTIwNzEgMS41MjA0MSA5Ljc4NDMzIDIuMDMwNDdDMTAuNDIxMiAyLjQwNjcxIDEwLjk1MjMgMi44OTg1NiAxMS4zNjE4IDMuNTE1OTNDMTEuODIwNCA0LjIwNzA4IDEyLjA3MDIgNC45NjQwMyAxMi4wNTYxIDUuNzk5NTRDMTIuMDU0MiA1LjkxOTMxIDEyLjA4MSA1Ljk2MDUxIDEyLjIxMzkgNS45MTgwM0MxMi41NDQxIDUuODEyOTYgMTIuODY1MSA1LjgyMDk0IDEzLjE0NDYgNi4wNjE3NkMxMy4yNDM5IDYuMTQ3MDMgMTMuMjY5MSA2LjA5ODE3IDEzLjI3MzkgNS45OTgyQzEzLjI4NTEgNS43NjQwOSAxMy4yNzMgNS41MzEyNiAxMy4yNTM4IDUuMjk3NDdDMTMuMTk0NCA0LjU2OTU5IDEyLjk4NDYgMy44ODY3NCAxMi42MjM0IDMuMjU3MjNDMTEuNzc3IDEuNzgyNjMgMTAuNDkzNCAwLjg1MjU3NiA4Ljg5NDg0IDAuMzQyMTk4QzguMTUzNTUgMC4xMDUyMTQgNy4zODc2NiAtMC4wMjM4MTgxIDYuNzU3NTEgMC4wMDM2NDkwN0M1LjYxNTcxIDAuMDAxNzMyNzYgNC42NjY4MSAwLjE4NjAxOCAzLjc1OTQ0IDAuNTc0MzkxQzIuMzAzNjggMS4xOTgxNSAxLjE1ODM2IDIuMTY2ODUgMC40ODU3MzMgMy42MjIyOUMtMC4xNTkxMDcgNS4wMTc2OSAtMC4xNTc4MjkgNi40MzM4NCAwLjQ2NTI5MiA3Ljg0MTA2QzAuODA4NjMyIDguNjE2NTIgMS4zMzIxMSA5LjI2NDg4IDEuOTQzNDEgOS44NDQyNEMyLjA1NTE5IDkuOTQ5OTYgMi4xMDQwNiAxMC4wNTU3IDIuMDgxNyAxMC4yMDhDMi4wNTIwOCAxMC40MDggMi4wMjUwMyAxMC42MDg0IDEuOTk3OTcgMTAuODA4OEMxLjk2MTcyIDExLjA3NzMgMS45MjU0NyAxMS4zNDU4IDEuODgzMDUgMTEuNjEzM0MxLjg0NjMyIDExLjg0NDkgMS45MTQwMyAxMi4wMjIxIDIuMDk2NzEgMTIuMTU4NUMyLjI3Mzk3IDEyLjI5MDcgMi40NjI3MyAxMi4yODM3IDIuNjUzMDggMTIuMTg2OUMzLjExNzU1IDExLjk1MTEgMy41ODIwMSAxMS43MTQ2IDQuMDQ2MjggMTEuNDc4Mkw0LjI3Mzk3IDExLjM2MjNDNC40NTE1NSAxMS4yNzE5IDQuNjIxNzggMTEuMjAyNiA0LjgzMzIxIDExLjI4NjZDNC45NTgzMiAxMS4zMzYyIDUuMDk1NjkgMTEuMzU2OCA1LjIzMjEgMTEuMzc3M0g1LjIzMjExTDUuMjMyMTMgMTEuMzc3M0w1LjIzMjE1IDExLjM3NzNMNS4yMzIxNyAxMS4zNzczQzUuMjcwNTUgMTEuMzgzIDUuMzA4ODUgMTEuMzg4OCA1LjM0Njc4IDExLjM5NTJDNi4wNzQ5OCAxMS41MTY5IDYuODA3NjUgMTEuNTMyOCA3LjU0MDMzIDExLjQ1MjdDOC4wNDg3OSAxMS4zOTcxIDguNTQ1NzUgMTEuMjc4OSA5LjAzMTU0IDExLjExODZDOS4wMzgwNSAxMS4xMTY0IDkuMDQ0ODggMTEuMTE0NSA5LjA1MTc4IDExLjExMjVDOS4wODY5IDExLjEwMjUgOS4xMjQwOSAxMS4wOTE4IDkuMTMxODMgMTEuMDUxNUM5LjEzNzcgMTEuMDIxMSA5LjEwOTQ1IDExLjAwODUgOS4wODM2MSAxMC45OTdDOS4wNzg1MiAxMC45OTQ3IDkuMDczNTMgMTAuOTkyNSA5LjA2ODkxIDEwLjk5MDJIOS4wNjg1OVpNMTEuNjMxIDguNDAzNUMxMS43NjEzIDguMjc3MDIgMTEuNzczNSA4LjE0ODMxIDExLjY3MTkgOC4wMzk0QzExLjU3MzIgNy45MzM2OCAxMS40NCA3Ljk0Mjk1IDExLjMxIDguMDY0NjNDMTEuMjkzOCA4LjA3OTc5IDExLjI3ODIgOC4wOTU0IDExLjI2MjYgOC4xMTEwN0wxMS4yNjI2IDguMTExMDhMMTEuMjYyNiA4LjExMTA5TDExLjI1MzIgOC4xMjA1MkMxMC44ODg0IDguNDg2MjIgMTAuNDYxNCA4LjczOTQ5IDkuOTU1MiA4Ljg2MDg2QzkuOTExMDUgOC44NzE0MyA5Ljg2NjU4IDguODgwNSA5LjgyMjEyIDguODg5NTZDOS42NjY1MSA4LjkyMTI3IDkuNTEwODkgOC45NTI5OCA5LjM2ODggOS4wNDk2MkM5LjAyNzM4IDkuMjgxODEgOC44NTkwNiA5LjY4MiA4Ljk2OTg5IDEwLjA3MzJDOS4wOTEyNiAxMC41MDE5IDkuNDIzNzQgMTAuNzYwNiA5Ljg1MzYzIDEwLjc2MTVDMTAuMzM5MSAxMC43NjIyIDEwLjcwOTYgMTAuNDU4MSAxMC43OTcxIDkuOTgzODJDMTAuOTEwNSA5LjM2OTk2IDExLjE4MDcgOC44Mzk0NiAxMS42MzEgOC40MDMxOFY4LjQwMzVaTTE0LjE1NzcgOS42OTA2MkMxNC4yOTA5IDkuMDYwNzkgMTQuODc2OSA4Ljc1MzU1IDE1LjQ0MjMgOC45ODI4NlY4Ljk4MzE4QzE1Ljc5NTIgOS4xMjY1OSAxNi4wMDUzIDkuNDY2NDEgMTUuOTk5OSA5Ljg4NjA5QzE1Ljk5NDEgMTAuMzM3NCAxNS42NjEgMTAuNzA2MyAxNS4xODMyIDEwLjc4NDhDMTQuNjQ4MyAxMC44NzI3IDE0LjE4NDggMTEuMTAyNiAxMy43ODg1IDExLjQ3MDlDMTMuNzU3MSAxMS40OTk5IDEzLjcyNjUgMTEuNTMgMTMuNjk2IDExLjU2MDFMMTMuNjYyNiAxMS41OTI5QzEzLjUxOCAxMS43MzMxIDEzLjM3NzEgMTEuNzUzMiAxMy4yNzIgMTEuNjQ4MUMxMy4xNzA1IDExLjU0NjMgMTMuMTc5MSAxMS40MDY3IDEzLjMxOTkgMTEuMjY5N0MxMy43NzAzIDEwLjgzMjQgMTQuMDI4IDEwLjMwMjkgMTQuMTU3NyA5LjY5MDYyWk0xMi42MzAxIDExLjUxMjFDMTIuNTk2OSAxMS41MDUzIDEyLjU2MzYgMTEuNDk5MiAxMi41MzAzIDExLjQ5MzJDMTIuNDUzNyAxMS40NzkyIDEyLjM3NzIgMTEuNDY1MyAxMi4zMDMzIDExLjQ0MjhDMTEuODE2NiAxMS4yOTQ5IDExLjM4NTEgMTEuMDU2NiAxMS4wMzk4IDEwLjY3MjdDMTAuOTMxNiAxMC41NTIzIDEwLjc2NDUgMTAuNTUyIDEwLjY2ODQgMTAuNjQ4NUMxMC41Nzg2IDEwLjczODggMTAuNTg1MyAxMC44OTE4IDEwLjcwNjEgMTEuMDAzOUMxMS4xODcxIDExLjQ0ODggMTEuNDQyOSAxMi4wMDY4IDExLjU2NjggMTIuNjQwOEMxMS42NTQzIDEzLjA4OTIgMTIuMDI1NSAxMy4zNzYzIDEyLjQ5NTYgMTMuMzc3M0MxMi42MDYxIDEzLjM4NCAxMi43MjQzIDEzLjM1NTYgMTIuODM3MyAxMy4zMDQ4QzEzLjIyNjcgMTMuMTI5OCAxMy40NDkgMTIuNzUyNiAxMy40MDU1IDEyLjM0MTVDMTMuMzYyNyAxMS45MzY5IDEzLjA0NDkgMTEuNTk3NCAxMi42Mjk3IDExLjUxMjRMMTIuNjMwMSAxMS41MTIxWk0xMS41NDM4IDcuMTM0NThDMTEuNTk0MyA2LjY3MDE5IDEyLjAxMDggNi4zMDA5OCAxMi40ODQxIDYuMzAxNjJIMTIuNDg0NEMxMi45MjggNi4zMDIyNiAxMy4zMjkyIDYuNjM2NjYgMTMuNDAyNiA3LjA4NDc2QzEzLjUwNTUgNy43MTIwMyAxMy43OTggOC4yMzQ1NCAxNC4yNDcxIDguNjc2MjVDMTQuMzczMyA4LjgwMDgxIDE0LjM3OSA4LjkwMjA2IDE0LjI2OTEgOS4wMjI3OUMxNC4xNzAxIDkuMTMxNyAxNC4wNjE1IDkuMTM4NDEgMTMuOTM2MyA5LjAxNjcyQzEzLjY0IDguNzI4MzEgMTMuMzE1OCA4LjQ4NDMgMTIuOTMgOC4zMjcxN0MxMi43NDE2IDguMjUwMTkgMTIuNTQ0OCA4LjIwNTMyIDEyLjM0ODEgOC4xNjA0OEMxMi4zMjIgOC4xNTQ1MyAxMi4yOTU5IDguMTQ4NTggMTIuMjY5OCA4LjE0MjU2QzExLjgxNCA4LjAzNzQ4IDExLjQ5NCA3LjU5NDUgMTEuNTQzOCA3LjEzNDU4WiIgZmlsbD0iY3VycmVudENvbG9yIi8+Cjwvc3ZnPg==",
Url: "",
LoginRedirectURL: redirectURL,
SignUpRedirectURL: redirectURL,
RankAgentEnabled: false,
UserStatusAgentEnabled: false,
UserRoleAgentEnabled: false,
MustAuthEmailEnabled: true,
EnabledOriginalUserSystem: true,
}
return desc
}
func (uc *UserCenter) ControlCenterItems() []plugin.ControlCenter {
var controlCenterItems []plugin.ControlCenter
return controlCenterItems
}
func (uc *UserCenter) LoginCallback(ctx *plugin.GinContext) (userInfo *plugin.UserCenterBasicUserInfo, err error) {
code := ctx.Query("code")
if len(code) == 0 {
return nil, fmt.Errorf("code is empty")
}
state := ctx.Query("state")
if len(state) == 0 {
return nil, fmt.Errorf("state is empty")
}
log.Debugf("request code: %s, state: %s", code, state)
info, err := uc.Company.AuthUser(code)
if err != nil {
return nil, fmt.Errorf("auth user failed: %w", err)
}
if !info.IsAvailable {
return nil, fmt.Errorf("user is not available")
}
if len(info.GetEmail()) == 0 {
ctx.Redirect(http.StatusFound, "/user-center/auth-failed")
ctx.Abort()
return nil, fmt.Errorf("user email is empty")
}
userInfo = &plugin.UserCenterBasicUserInfo{}
userInfo.ExternalID = info.Userid
userInfo.Username = info.Userid
userInfo.DisplayName = info.Name
userInfo.Email = info.GetEmail()
userInfo.Rank = 0
userInfo.Avatar = info.Avatar
userInfo.Mobile = info.Mobile
uc.Cache.Set(state, userInfo.ExternalID, time.Minute*5)
return userInfo, nil
}
func (uc *UserCenter) SignUpCallback(ctx *plugin.GinContext) (userInfo *plugin.UserCenterBasicUserInfo, err error) {
return uc.LoginCallback(ctx)
}
func (uc *UserCenter) UserInfo(externalID string) (userInfo *plugin.UserCenterBasicUserInfo, err error) {
userDetailInfo := uc.Company.UserDetailInfoMapping[externalID]
if userDetailInfo == nil {
userDetailInfo, err = uc.Company.GetUserDetailInfo(externalID)
if err != nil {
log.Errorf("get user detail info failed: %v", err)
userInfo = &plugin.UserCenterBasicUserInfo{
ExternalID: externalID,
Status: plugin.UserStatusDeleted,
}
return userInfo, nil
}
}
userInfo = &plugin.UserCenterBasicUserInfo{
ExternalID: externalID,
Username: userDetailInfo.Userid,
DisplayName: userDetailInfo.Name,
Bio: uc.Company.formatDepartmentAndPosition(userDetailInfo.Department, userDetailInfo.Position),
}
switch userDetailInfo.Status {
case 1:
userInfo.Status = plugin.UserStatusAvailable
case 2:
userInfo.Status = plugin.UserStatusSuspended
default:
userInfo.Status = plugin.UserStatusDeleted
}
return userInfo, nil
}
func (uc *UserCenter) UserList(externalIDs []string) (userList []*plugin.UserCenterBasicUserInfo, err error) {
userList = make([]*plugin.UserCenterBasicUserInfo, 0)
return userList, nil
}
func (uc *UserCenter) UserSettings(externalID string) (userSettings *plugin.SettingInfo, err error) {
return &plugin.SettingInfo{
ProfileSettingRedirectURL: "",
AccountSettingRedirectURL: "",
}, nil
}
func (uc *UserCenter) PersonalBranding(externalID string) (branding []*plugin.PersonalBranding) {
return branding
}
func (uc *UserCenter) asyncCompany() {
go func() {
defer func() {
if err := recover(); err != nil {
log.Errorf("sync data panic: %s", err)
}
}()
uc.syncCompany()
}()
}
func (uc *UserCenter) syncCompany() {
if !uc.syncLock.TryLock() {
log.Infof("sync data is running")
return
}
defer func() {
uc.syncing = false
if uc.syncSuccess {
uc.syncTime = time.Now()
}
uc.syncLock.Unlock()
}()
log.Info("start sync company data")
uc.syncing = true
uc.syncSuccess = true
if err := uc.Company.ListDepartmentAll(); err != nil {
log.Errorf("list department error: %s", err)
uc.syncSuccess = false
return
}
if err := uc.Company.ListUser(); err != nil {
log.Errorf("list user error: %s", err)
uc.syncSuccess = false
return
}
log.Info("end sync company data")
}