diff --git a/etc/webapi.conf b/etc/webapi.conf index 4f7254c9..7f9abf13 100644 --- a/etc/webapi.conf +++ b/etc/webapi.conf @@ -149,6 +149,7 @@ Email = "mail" [OIDC] Enable = false +DiaplayName = "OIDC登录" RedirectURL = "http://n9e.com/callback" SsoAddr = "http://sso.example.org" ClientId = "" @@ -161,6 +162,52 @@ Nickname = "nickname" Phone = "phone_number" Email = "email" +[CAS] +Enable = false +DiaplayName = "CAS登录" +SsoAddr = "https://cas.example.com/cas/" +RedirectURL = "http://127.0.0.1:18000/callback/cas" +CoverAttributes = false +# cas user default roles +DefaultRoles = ["Standard"] + +[CAS.Attributes] +Nickname = "nickname" +Phone = "phone_number" +Email = "email" + +[OAuth] +Enable = false +DisplayName = "OAuth2登录" +RedirectURL = "http://127.0.0.1:18000/callback/oauth" +SsoAddr = "https://sso.example.com/oauth2/authorize" +TokenAddr = "https://sso.example.com/oauth2/token" +UserInfoAddr = "https://api.example.com/api/v1/user/info" +ClientId = "" +ClientSecret = "" +CoverAttributes = true +DefaultRoles = ["Standard"] +UserinfoIsArray = false +UserinfoPrefix = "data" +Scopes = ["profile", "email", "phone"] + +[OAuth.Attributes] +# Username must be defined +Username = "username" +Nickname = "nickname" +Phone = "phone_number" +Email = "email" + +# example +# # nested : UserinfoIsArray=false, UserinfoPrefix="data" +# # {"data":{"username":"123456","nickname":"姓名"},"code":0,"message":"ok"} +# # nested and array : UserinfoIsArray=true, UserinfoPrefix="data" +# # {"data":[{"username":"123456","nickname":"姓名"}],"code":0,"message":"ok"} +# # flat : UserinfoIsArray=false, UserinfoPrefix="" +# # {"username":"123456","nickname":"姓名"} +# # flat and array : UserinfoIsArray=true, UserinfoPrefix="" +# # [{"username":"123456","nickname":"姓名"}] + [Redis] # address, ip:port or ip1:port,ip2:port for cluster and sentinel(SentinelAddrs) Address = "127.0.0.1:6379" diff --git a/go.mod b/go.mod index 3dedb3a2..060386c2 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/prometheus/common v0.32.1 github.com/prometheus/prometheus v2.5.0+incompatible github.com/tidwall/gjson v1.14.0 - github.com/toolkits/pkg v1.2.9 + github.com/toolkits/pkg v1.3.1-0.20220824084030-9f9f830a05d5 github.com/urfave/cli/v2 v2.3.0 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df diff --git a/go.sum b/go.sum index fb041858..4dfdc0bd 100644 --- a/go.sum +++ b/go.sum @@ -368,8 +368,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/toolkits/pkg v1.2.9 h1:zGlrJDl+2sMBoxBRIoMtAwvKmW5wctuji2+qHCecMKk= -github.com/toolkits/pkg v1.2.9/go.mod h1:ZUsQAOoaR99PSbes+RXSirvwmtd6+XIUvizCmrjfUYc= +github.com/toolkits/pkg v1.3.1-0.20220824084030-9f9f830a05d5 h1:kMCwr2gNHjHEVgw+uNVdiPbGadj4TekbIfrTXElZeI0= +github.com/toolkits/pkg v1.3.1-0.20220824084030-9f9f830a05d5/go.mod h1:PvTBg/UxazPgBz6VaCM7FM7kJldjfVrsuN6k4HT/VuY= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= diff --git a/src/pkg/cas/cas.go b/src/pkg/cas/cas.go new file mode 100644 index 00000000..1b81f4de --- /dev/null +++ b/src/pkg/cas/cas.go @@ -0,0 +1,150 @@ +package cas + +import ( + "bytes" + "context" + "net/url" + "strings" + "time" + + "github.com/didi/nightingale/v5/src/storage" + "github.com/google/uuid" + "github.com/toolkits/pkg/cas" + "github.com/toolkits/pkg/logger" +) + +type Config struct { + Enable bool + SsoAddr string + RedirectURL string + DisplayName string + CoverAttributes bool + Attributes struct { + Nickname string + Phone string + Email string + } + DefaultRoles []string +} + +type ssoClient struct { + config Config + ssoAddr string + callbackAddr string + displayName string + attributes struct { + nickname string + phone string + email string + } +} + +var ( + cli ssoClient +) + +func Init(cf Config) { + if !cf.Enable { + return + } + cli = ssoClient{} + cli.config = cf + cli.ssoAddr = cf.SsoAddr + cli.callbackAddr = cf.RedirectURL + cli.displayName = cf.DisplayName + cli.attributes.nickname = cf.Attributes.Nickname + cli.attributes.phone = cf.Attributes.Phone + cli.attributes.email = cf.Attributes.Email +} + +func GetDisplayName() string { + return cli.displayName +} + +// Authorize return the cas authorize location and state +func Authorize(redirect string) (string, string, error) { + state := uuid.New().String() + ctx := context.Background() + err := storage.Redis.Set(ctx, wrapStateKey(state), redirect, time.Duration(300*time.Second)).Err() + if err != nil { + return "", "", err + } + return cli.genRedirectURL(state), state, nil +} + +func fetchRedirect(ctx context.Context, state string) (string, error) { + return storage.Redis.Get(ctx, wrapStateKey(state)).Result() +} + +func deleteRedirect(ctx context.Context, state string) error { + return storage.Redis.Del(ctx, wrapStateKey(state)).Err() +} + +func wrapStateKey(key string) string { + return "n9e_cas_" + key +} + +func (cli *ssoClient) genRedirectURL(state string) string { + var buf bytes.Buffer + buf.WriteString(cli.ssoAddr + "login") + v := url.Values{ + "service": {cli.callbackAddr}, + } + if strings.Contains(cli.ssoAddr, "?") { + buf.WriteByte('&') + } else { + buf.WriteByte('?') + } + buf.WriteString(v.Encode()) + return buf.String() +} + +type CallbackOutput struct { + Redirect string `json:"redirect"` + Msg string `json:"msg"` + AccessToken string `json:"accessToken"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Phone string `yaml:"phone"` + Email string `yaml:"email"` +} + +func ValidateServiceTicket(ctx context.Context, ticket, state string) (ret *CallbackOutput, err error) { + casUrl, err := url.Parse(cli.config.SsoAddr) + if err != nil { + logger.Error(err) + return + } + serviceUrl, err := url.Parse(cli.callbackAddr) + if err != nil { + logger.Error(err) + return + } + resOptions := &cas.RestOptions{ + CasURL: casUrl, + ServiceURL: serviceUrl, + } + resCli := cas.NewRestClient(resOptions) + authRet, err := resCli.ValidateServiceTicket(cas.ServiceTicket(ticket)) + if err != nil { + logger.Errorf("Ticket Validating Failed: %s", err) + return + } + ret = &CallbackOutput{} + ret.Username = authRet.User + ret.Nickname = authRet.Attributes.Get(cli.attributes.nickname) + logger.Debugf("CAS Authentication Response's Attributes--[Nickname]: %s", ret.Nickname) + ret.Email = authRet.Attributes.Get(cli.attributes.email) + logger.Debugf("CAS Authentication Response's Attributes--[Email]: %s", ret.Email) + ret.Phone = authRet.Attributes.Get(cli.attributes.phone) + logger.Debugf("CAS Authentication Response's Attributes--[Phone]: %s", ret.Phone) + ret.Redirect, err = fetchRedirect(ctx, state) + if err != nil { + logger.Debugf("get redirect err:%s state:%s", state, err) + } + err = deleteRedirect(ctx, state) + if err != nil { + logger.Debugf("delete redirect err:%s state:%s", state, err) + } + return +} diff --git a/src/pkg/oauth2x/oauth2x.go b/src/pkg/oauth2x/oauth2x.go new file mode 100644 index 00000000..0c8af2ec --- /dev/null +++ b/src/pkg/oauth2x/oauth2x.go @@ -0,0 +1,205 @@ +package oauth2x + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + "time" + + "github.com/didi/nightingale/v5/src/storage" + "github.com/toolkits/pkg/logger" + + "github.com/google/uuid" + jsoniter "github.com/json-iterator/go" + "golang.org/x/oauth2" +) + +type ssoClient struct { + config oauth2.Config + ssoAddr string + userInfoAddr string + callbackAddr string + displayName string + coverAttributes bool + attributes struct { + username string + nickname string + phone string + email string + } + userinfoIsArray bool + userinfoPrefix string +} + +type Config struct { + Enable bool + DisplayName string + RedirectURL string + SsoAddr string + TokenAddr string + UserInfoAddr string + ClientId string + ClientSecret string + CoverAttributes bool + Attributes struct { + Username string + Nickname string + Phone string + Email string + } + DefaultRoles []string + UserinfoIsArray bool + UserinfoPrefix string + Scopes []string +} + +var ( + cli ssoClient +) + +func Init(cf Config) { + if !cf.Enable { + return + } + + cli.ssoAddr = cf.SsoAddr + cli.userInfoAddr = cf.UserInfoAddr + cli.callbackAddr = cf.RedirectURL + cli.displayName = cf.DisplayName + cli.coverAttributes = cf.CoverAttributes + cli.attributes.username = cf.Attributes.Username + cli.attributes.nickname = cf.Attributes.Nickname + cli.attributes.phone = cf.Attributes.Phone + cli.attributes.email = cf.Attributes.Email + cli.userinfoIsArray = cf.UserinfoIsArray + cli.userinfoPrefix = cf.UserinfoPrefix + + cli.config = oauth2.Config{ + ClientID: cf.ClientId, + ClientSecret: cf.ClientSecret, + Endpoint: oauth2.Endpoint{ + AuthURL: cf.SsoAddr, + TokenURL: cf.TokenAddr, + }, + RedirectURL: cf.RedirectURL, + Scopes: cf.Scopes, + } +} + +func GetDisplayName() string { + return cli.displayName +} + +func wrapStateKey(key string) string { + return "n9e_oauth_" + key +} + +// Authorize return the sso authorize location with state +func Authorize(redirect string) (string, error) { + state := uuid.New().String() + ctx := context.Background() + + err := storage.Redis.Set(ctx, wrapStateKey(state), redirect, time.Duration(300*time.Second)).Err() + if err != nil { + return "", err + } + + return cli.config.AuthCodeURL(state), nil +} + +func fetchRedirect(ctx context.Context, state string) (string, error) { + return storage.Redis.Get(ctx, wrapStateKey(state)).Result() +} + +func deleteRedirect(ctx context.Context, state string) error { + return storage.Redis.Del(ctx, wrapStateKey(state)).Err() +} + +// Callback 用 code 兑换 accessToken 以及 用户信息 +func Callback(ctx context.Context, code, state string) (*CallbackOutput, error) { + ret, err := exchangeUser(code) + if err != nil { + return nil, fmt.Errorf("ilegal user:%v", err) + } + + ret.Redirect, err = fetchRedirect(ctx, state) + if err != nil { + logger.Errorf("get redirect err:%v code:%s state:%s", code, state, err) + } + + err = deleteRedirect(ctx, state) + if err != nil { + logger.Errorf("delete redirect err:%v code:%s state:%s", code, state, err) + } + return ret, nil +} + +type CallbackOutput struct { + Redirect string `json:"redirect"` + Msg string `json:"msg"` + AccessToken string `json:"accessToken"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Phone string `yaml:"phone"` + Email string `yaml:"email"` +} + +func exchangeUser(code string) (*CallbackOutput, error) { + ctx := context.Background() + oauth2Token, err := cli.config.Exchange(ctx, code) + if err != nil { + return nil, fmt.Errorf("failed to exchange token: %s", err) + } + + userInfo, err := getUserInfo(cli.userInfoAddr, oauth2Token.AccessToken) + if err != nil { + logger.Errorf("failed to get user info: %s", err) + return nil, fmt.Errorf("failed to get user info: %s", err) + } + + return &CallbackOutput{ + AccessToken: oauth2Token.AccessToken, + Username: getUserinfoField(userInfo, cli.userinfoIsArray, cli.userinfoPrefix, cli.attributes.username), + Nickname: getUserinfoField(userInfo, cli.userinfoIsArray, cli.userinfoPrefix, cli.attributes.nickname), + Phone: getUserinfoField(userInfo, cli.userinfoIsArray, cli.userinfoPrefix, cli.attributes.phone), + Email: getUserinfoField(userInfo, cli.userinfoIsArray, cli.userinfoPrefix, cli.attributes.email), + }, nil +} + +func getUserInfo(userInfoAddr, accessToken string) ([]byte, error) { + r, err := http.NewRequest("GET", userInfoAddr, nil) + if err != nil { + return nil, err + } + + r.Header.Add("Authorization", "Bearer "+accessToken) + + resp, err := http.DefaultClient.Do(r) + if err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, nil + } + return body, err +} + +func getUserinfoField(input []byte, isArray bool, prefix, field string) string { + if prefix == "" { + if isArray { + return jsoniter.Get(input, 0).Get(field).ToString() + } else { + return jsoniter.Get(input, field).ToString() + } + } else { + if isArray { + return jsoniter.Get(input, prefix, 0).Get(field).ToString() + } else { + return jsoniter.Get(input, prefix).Get(field).ToString() + } + } +} diff --git a/src/pkg/oidcc/oidc.go b/src/pkg/oidcc/oidc.go index ba7abb18..1ec472ee 100644 --- a/src/pkg/oidcc/oidc.go +++ b/src/pkg/oidcc/oidc.go @@ -20,6 +20,7 @@ type ssoClient struct { ssoAddr string callbackAddr string coverAttributes bool + displayName string attributes struct { username string nickname string @@ -30,6 +31,7 @@ type ssoClient struct { type Config struct { Enable bool + DisplayName string RedirectURL string SsoAddr string ClientId string @@ -59,6 +61,7 @@ func Init(cf Config) { cli.attributes.nickname = cf.Attributes.Nickname cli.attributes.phone = cf.Attributes.Phone cli.attributes.email = cf.Attributes.Email + cli.displayName = cf.DisplayName provider, err := oidc.NewProvider(context.Background(), cf.SsoAddr) if err != nil { log.Fatal(err) @@ -77,6 +80,10 @@ func Init(cf Config) { } } +func GetDisplayName() string { + return cli.displayName +} + func wrapStateKey(key string) string { return "n9e_oidc_" + key } diff --git a/src/webapi/config/config.go b/src/webapi/config/config.go index 8bc58168..4740c174 100644 --- a/src/webapi/config/config.go +++ b/src/webapi/config/config.go @@ -9,9 +9,11 @@ import ( "github.com/gin-gonic/gin" "github.com/koding/multiconfig" + "github.com/didi/nightingale/v5/src/pkg/cas" "github.com/didi/nightingale/v5/src/pkg/httpx" "github.com/didi/nightingale/v5/src/pkg/ldapx" "github.com/didi/nightingale/v5/src/pkg/logx" + "github.com/didi/nightingale/v5/src/pkg/oauth2x" "github.com/didi/nightingale/v5/src/pkg/oidcc" "github.com/didi/nightingale/v5/src/pkg/ormx" "github.com/didi/nightingale/v5/src/pkg/secu" @@ -135,6 +137,8 @@ type Config struct { Clusters []ClusterOptions Ibex Ibex OIDC oidcc.Config + CAS cas.Config + OAuth oauth2x.Config TargetMetrics map[string]string } diff --git a/src/webapi/router/router.go b/src/webapi/router/router.go index 871e01e2..a903ca1d 100644 --- a/src/webapi/router/router.go +++ b/src/webapi/router/router.go @@ -134,8 +134,13 @@ func configRoute(r *gin.Engine, version string) { pages.POST("/auth/logout", jwtMock(), logoutPost) pages.POST("/auth/refresh", jwtMock(), refreshPost) + pages.GET("/auth/sso-config", ssoConfigGet) pages.GET("/auth/redirect", loginRedirect) + pages.GET("/auth/redirect/cas", loginRedirectCas) + pages.GET("/auth/redirect/oauth", loginRedirectOAuth) pages.GET("/auth/callback", loginCallback) + pages.GET("/auth/callback/cas", loginCallbackCas) + pages.GET("/auth/callback/oauth", loginCallbackOAuth) pages.GET("/metrics/desc", metricsDescGetFile) pages.POST("/metrics/desc", metricsDescGetMap) diff --git a/src/webapi/router/router_login.go b/src/webapi/router/router_login.go index a6037eaa..a3819e4c 100644 --- a/src/webapi/router/router_login.go +++ b/src/webapi/router/router_login.go @@ -13,6 +13,8 @@ import ( "github.com/toolkits/pkg/logger" "github.com/didi/nightingale/v5/src/models" + "github.com/didi/nightingale/v5/src/pkg/cas" + "github.com/didi/nightingale/v5/src/pkg/oauth2x" "github.com/didi/nightingale/v5/src/pkg/oidcc" "github.com/didi/nightingale/v5/src/webapi/config" ) @@ -259,3 +261,212 @@ func loginCallback(c *gin.Context) { RefreshToken: ts.RefreshToken, }, nil) } + +type RedirectOutput struct { + Redirect string `json:"redirect"` + State string `json:"state"` +} + +func loginRedirectCas(c *gin.Context) { + redirect := ginx.QueryStr(c, "redirect", "/") + + v, exists := c.Get("userid") + if exists { + userid := v.(int64) + user, err := models.UserGetById(userid) + ginx.Dangerous(err) + if user == nil { + ginx.Bomb(200, "user not found") + } + + if user.Username != "" { // already login + ginx.NewRender(c).Data(redirect, nil) + return + } + } + + if !config.C.CAS.Enable { + logger.Error("cas is not enable") + ginx.NewRender(c).Data("", nil) + return + } + + redirect, state, err := cas.Authorize(redirect) + + ginx.Dangerous(err) + ginx.NewRender(c).Data(RedirectOutput{ + Redirect: redirect, + State: state, + }, err) +} + +func loginCallbackCas(c *gin.Context) { + ticket := ginx.QueryStr(c, "ticket", "") + state := ginx.QueryStr(c, "state", "") + ret, err := cas.ValidateServiceTicket(c.Request.Context(), ticket, state) + if err != nil { + logger.Errorf("ValidateServiceTicket: %s", err) + ginx.NewRender(c).Data("", err) + return + } + user, err := models.UserGet("username=?", ret.Username) + if err != nil { + logger.Errorf("UserGet: %s", err) + } + ginx.Dangerous(err) + if user != nil { + if config.C.CAS.CoverAttributes { + user.Nickname = ret.Nickname + user.Email = ret.Email + user.Phone = ret.Phone + user.UpdateAt = time.Now().Unix() + ginx.Dangerous(user.Update("email", "nickname", "phone", "update_at")) + } + } else { + now := time.Now().Unix() + user = &models.User{ + Username: ret.Username, + Password: "******", + Nickname: ret.Nickname, + Portrait: "", + Roles: strings.Join(config.C.CAS.DefaultRoles, " "), + RolesLst: config.C.CAS.DefaultRoles, + Contacts: []byte("{}"), + Phone: ret.Phone, + Email: ret.Email, + CreateAt: now, + UpdateAt: now, + CreateBy: "CAS", + UpdateBy: "CAS", + } + // create user from cas + ginx.Dangerous(user.Add()) + } + + // set user login state + userIdentity := fmt.Sprintf("%d-%s", user.Id, user.Username) + ts, err := createTokens(config.C.JWTAuth.SigningKey, userIdentity) + if err != nil { + logger.Errorf("createTokens: %s", err) + } + ginx.Dangerous(err) + ginx.Dangerous(createAuth(c.Request.Context(), userIdentity, ts)) + + redirect := "/" + if ret.Redirect != "/login" { + redirect = ret.Redirect + } + ginx.NewRender(c).Data(CallbackOutput{ + Redirect: redirect, + User: user, + AccessToken: ts.AccessToken, + RefreshToken: ts.RefreshToken, + }, nil) +} + +func loginRedirectOAuth(c *gin.Context) { + redirect := ginx.QueryStr(c, "redirect", "/") + + v, exists := c.Get("userid") + if exists { + userid := v.(int64) + user, err := models.UserGetById(userid) + ginx.Dangerous(err) + if user == nil { + ginx.Bomb(200, "user not found") + } + + if user.Username != "" { // already login + ginx.NewRender(c).Data(redirect, nil) + return + } + } + + if !config.C.OAuth.Enable { + ginx.NewRender(c).Data("", nil) + return + } + + redirect, err := oauth2x.Authorize(redirect) + ginx.Dangerous(err) + + ginx.NewRender(c).Data(redirect, err) +} + +func loginCallbackOAuth(c *gin.Context) { + code := ginx.QueryStr(c, "code", "") + state := ginx.QueryStr(c, "state", "") + + ret, err := oauth2x.Callback(c.Request.Context(), code, state) + if err != nil { + logger.Debugf("sso.callback() get ret %+v error %v", ret, err) + ginx.NewRender(c).Data(CallbackOutput{}, err) + return + } + + user, err := models.UserGet("username=?", ret.Username) + ginx.Dangerous(err) + + if user != nil { + if config.C.OAuth.CoverAttributes { + user.Nickname = ret.Nickname + user.Email = ret.Email + user.Phone = ret.Phone + user.UpdateAt = time.Now().Unix() + + user.Update("email", "nickname", "phone", "update_at") + } + } else { + now := time.Now().Unix() + user = &models.User{ + Username: ret.Username, + Password: "******", + Nickname: ret.Nickname, + Phone: ret.Phone, + Email: ret.Email, + Portrait: "", + Roles: strings.Join(config.C.OAuth.DefaultRoles, " "), + RolesLst: config.C.OAuth.DefaultRoles, + Contacts: []byte("{}"), + CreateAt: now, + UpdateAt: now, + CreateBy: "oauth2", + UpdateBy: "oauth2", + } + + // create user from oidc + ginx.Dangerous(user.Add()) + } + + // set user login state + userIdentity := fmt.Sprintf("%d-%s", user.Id, user.Username) + ts, err := createTokens(config.C.JWTAuth.SigningKey, userIdentity) + ginx.Dangerous(err) + ginx.Dangerous(createAuth(c.Request.Context(), userIdentity, ts)) + + redirect := "/" + if ret.Redirect != "/login" { + redirect = ret.Redirect + } + + ginx.NewRender(c).Data(CallbackOutput{ + Redirect: redirect, + User: user, + AccessToken: ts.AccessToken, + RefreshToken: ts.RefreshToken, + }, nil) +} + +type SsoConfigOutput struct { + OidcDisplayName string `json:"oidcDisplayName"` + CasDisplayName string `json:"casDisplayName"` + OauthDisplayName string `json:"oauthDisplayName"` +} + +func ssoConfigGet(c *gin.Context) { + ginx.NewRender(c).Data(SsoConfigOutput{ + OidcDisplayName: oidcc.GetDisplayName(), + CasDisplayName: cas.GetDisplayName(), + OauthDisplayName: oauth2x.GetDisplayName(), + }, nil) +} diff --git a/src/webapi/webapi.go b/src/webapi/webapi.go index 12640e5a..b90eecf8 100644 --- a/src/webapi/webapi.go +++ b/src/webapi/webapi.go @@ -10,9 +10,11 @@ import ( "github.com/toolkits/pkg/i18n" "github.com/didi/nightingale/v5/src/models" + "github.com/didi/nightingale/v5/src/pkg/cas" "github.com/didi/nightingale/v5/src/pkg/httpx" "github.com/didi/nightingale/v5/src/pkg/ldapx" "github.com/didi/nightingale/v5/src/pkg/logx" + "github.com/didi/nightingale/v5/src/pkg/oauth2x" "github.com/didi/nightingale/v5/src/pkg/oidcc" "github.com/didi/nightingale/v5/src/storage" "github.com/didi/nightingale/v5/src/webapi/config" @@ -101,6 +103,12 @@ func (a Webapi) initialize() (func(), error) { // init oidc oidcc.Init(config.C.OIDC) + // init cas + cas.Init(config.C.CAS) + + // init oauth + oauth2x.Init(config.C.OAuth) + // init logger loggerClean, err := logx.Init(config.C.Log) if err != nil {