mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-12 00:37:51 +08:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5757021e87 | ||
![]() |
259a4e1307 | ||
![]() |
034d822dd5 | ||
![]() |
a8502d1173 | ||
![]() |
3c2f7b7fc8 | ||
![]() |
fbc73de3bb | ||
![]() |
479daf4fa4 | ||
![]() |
d129202b95 | ||
![]() |
c1f553440e | ||
![]() |
7dcae2d183 | ||
![]() |
5ec0c7a890 | ||
![]() |
051752340d | ||
![]() |
c87c001da3 | ||
![]() |
12bc419659 | ||
![]() |
d5f18f2d64 | ||
![]() |
02c06bc93c |
@@ -80,6 +80,7 @@ p, *, *, POST, /api/login, *, *
|
|||||||
p, *, *, GET, /api/get-app-login, *, *
|
p, *, *, GET, /api/get-app-login, *, *
|
||||||
p, *, *, POST, /api/logout, *, *
|
p, *, *, POST, /api/logout, *, *
|
||||||
p, *, *, GET, /api/get-account, *, *
|
p, *, *, GET, /api/get-account, *, *
|
||||||
|
p, *, *, GET, /api/userinfo, *, *
|
||||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
p, *, *, POST, /api/login/oauth/access_token, *, *
|
||||||
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
||||||
p, *, *, GET, /api/get-application, *, *
|
p, *, *, GET, /api/get-application, *, *
|
||||||
|
@@ -18,7 +18,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@@ -67,6 +69,18 @@ type Response struct {
|
|||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Userinfo struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Iss string `json:"iss"`
|
||||||
|
Aud string `json:"aud"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
DisplayName string `json:"preferred_username,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Avatar string `json:"picture,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Phone string `json:"phone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type HumanCheck struct {
|
type HumanCheck struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
@@ -231,6 +245,47 @@ func (c *ApiController) GetAccount() {
|
|||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserInfo
|
||||||
|
// @Title UserInfo
|
||||||
|
// @Tag Account API
|
||||||
|
// @Description return user information according to OIDC standards
|
||||||
|
// @Success 200 {object} controllers.Userinfo The Response object
|
||||||
|
// @router /userinfo [get]
|
||||||
|
func (c *ApiController) GetUserinfo() {
|
||||||
|
userId, ok := c.RequireSignedIn()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := object.GetUser(userId)
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scope, aud := c.GetSessionOidc()
|
||||||
|
iss := beego.AppConfig.String("origin")
|
||||||
|
resp := Userinfo{
|
||||||
|
Sub: user.Id,
|
||||||
|
Iss: iss,
|
||||||
|
Aud: aud,
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "profile") {
|
||||||
|
resp.Name = user.Name
|
||||||
|
resp.DisplayName = user.DisplayName
|
||||||
|
resp.Avatar = user.Avatar
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "email") {
|
||||||
|
resp.Email = user.Email
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "address") {
|
||||||
|
resp.Address = user.Location
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "phone") {
|
||||||
|
resp.Phone = user.Phone
|
||||||
|
}
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
// GetHumanCheck ...
|
// GetHumanCheck ...
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Title GetHumancheck
|
// @Title GetHumancheck
|
||||||
|
@@ -221,7 +221,7 @@ func (c *ApiController) Login() {
|
|||||||
clientSecret = provider.ClientSecret2
|
clientSecret = provider.ClientSecret2
|
||||||
}
|
}
|
||||||
|
|
||||||
idProvider := idp.GetIdProvider(provider.Type, clientId, clientSecret, form.RedirectUri)
|
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, form.RedirectUri)
|
||||||
if idProvider == nil {
|
if idProvider == nil {
|
||||||
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
||||||
return
|
return
|
||||||
|
@@ -72,6 +72,28 @@ func (c *ApiController) GetSessionUsername() string {
|
|||||||
return user.(string)
|
return user.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||||
|
sessionData := c.GetSessionData()
|
||||||
|
if sessionData != nil &&
|
||||||
|
sessionData.ExpireTime != 0 &&
|
||||||
|
sessionData.ExpireTime < time.Now().Unix() {
|
||||||
|
c.SetSessionUsername("")
|
||||||
|
c.SetSessionData(nil)
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
scopeValue := c.GetSession("scope")
|
||||||
|
audValue := c.GetSession("aud")
|
||||||
|
var scope, aud string
|
||||||
|
var ok bool
|
||||||
|
if scope, ok = scopeValue.(string); !ok {
|
||||||
|
scope = ""
|
||||||
|
}
|
||||||
|
if aud, ok = audValue.(string); !ok {
|
||||||
|
aud = ""
|
||||||
|
}
|
||||||
|
return scope, aud
|
||||||
|
}
|
||||||
|
|
||||||
// SetSessionUsername ...
|
// SetSessionUsername ...
|
||||||
func (c *ApiController) SetSessionUsername(user string) {
|
func (c *ApiController) SetSessionUsername(user string) {
|
||||||
c.SetSession("username", user)
|
c.SetSession("username", user)
|
||||||
|
@@ -16,6 +16,7 @@ services:
|
|||||||
db:
|
db:
|
||||||
restart: always
|
restart: always
|
||||||
image: mysql:8.0.25
|
image: mysql:8.0.25
|
||||||
|
platform: linux/amd64
|
||||||
ports:
|
ports:
|
||||||
- "3306:3306"
|
- "3306:3306"
|
||||||
environment:
|
environment:
|
||||||
|
116
idp/baidu.go
Normal file
116
idp/baidu.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed 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 idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaiduIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBaiduIdProvider(clientId string, clientSecret string, redirectUrl string) *BaiduIdProvider {
|
||||||
|
idp := &BaiduIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig()
|
||||||
|
config.ClientID = clientId
|
||||||
|
config.ClientSecret = clientSecret
|
||||||
|
config.RedirectURL = redirectUrl
|
||||||
|
idp.Config = config
|
||||||
|
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *BaiduIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *BaiduIdProvider) getConfig() *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://openapi.baidu.com/oauth/2.0/authorize",
|
||||||
|
TokenURL: "https://openapi.baidu.com/oauth/2.0/token",
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
Scopes: []string{"email"},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *BaiduIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
|
||||||
|
return idp.Config.Exchange(ctx, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"userid":"2097322476",
|
||||||
|
"username":"wl19871011",
|
||||||
|
"realname":"阳光",
|
||||||
|
"userdetail":"喜欢自由",
|
||||||
|
"birthday":"1987-01-01",
|
||||||
|
"marriage":"恋爱",
|
||||||
|
"sex":"男",
|
||||||
|
"blood":"O",
|
||||||
|
"constellation":"射手",
|
||||||
|
"figure":"小巧",
|
||||||
|
"education":"大学/专科",
|
||||||
|
"trade":"计算机/电子产品",
|
||||||
|
"job":"未知",
|
||||||
|
"birthday_year":"1987",
|
||||||
|
"birthday_month":"01",
|
||||||
|
"birthday_day":"01",
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type BaiduUserInfo struct {
|
||||||
|
OpenId string `json:"openid"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Portrait string `json:"portrait"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
resp, err := idp.Client.Get(fmt.Sprintf("https://openapi.baidu.com/rest/2.0/passport/users/getInfo?access_token=%s", token.AccessToken))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
baiduUser := BaiduUserInfo{}
|
||||||
|
if err = json.Unmarshal(data, &baiduUser); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: baiduUser.OpenId,
|
||||||
|
Username: baiduUser.Username,
|
||||||
|
DisplayName: baiduUser.Username,
|
||||||
|
AvatarUrl: fmt.Sprintf("https://himg.bdimg.com/sys/portrait/item/%s", baiduUser.Portrait),
|
||||||
|
}
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
@@ -35,33 +35,41 @@ type IdProvider interface {
|
|||||||
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string) IdProvider {
|
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, redirectUrl string) IdProvider {
|
||||||
if providerType == "GitHub" {
|
if typ == "GitHub" {
|
||||||
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
|
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "Google" {
|
} else if typ == "Google" {
|
||||||
return NewGoogleIdProvider(clientId, clientSecret, redirectUrl)
|
return NewGoogleIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "QQ" {
|
} else if typ == "QQ" {
|
||||||
return NewQqIdProvider(clientId, clientSecret, redirectUrl)
|
return NewQqIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "WeChat" {
|
} else if typ == "WeChat" {
|
||||||
return NewWeChatIdProvider(clientId, clientSecret, redirectUrl)
|
return NewWeChatIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "Facebook" {
|
} else if typ == "Facebook" {
|
||||||
return NewFacebookIdProvider(clientId, clientSecret, redirectUrl)
|
return NewFacebookIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "DingTalk" {
|
} else if typ == "DingTalk" {
|
||||||
return NewDingTalkIdProvider(clientId, clientSecret, redirectUrl)
|
return NewDingTalkIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "Weibo" {
|
} else if typ == "Weibo" {
|
||||||
return NewWeiBoIdProvider(clientId, clientSecret, redirectUrl)
|
return NewWeiBoIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "Gitee" {
|
} else if typ == "Gitee" {
|
||||||
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
|
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "LinkedIn" {
|
} else if typ == "LinkedIn" {
|
||||||
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl)
|
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "WeCom" {
|
} else if typ == "WeCom" {
|
||||||
return NewWeComIdProvider(clientId, clientSecret, redirectUrl)
|
if subType == "Internal" {
|
||||||
} else if providerType == "Lark" {
|
return NewWeComInternalIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
|
} else if subType == "Third-party" {
|
||||||
|
return NewWeComIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if typ == "Lark" {
|
||||||
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if providerType == "GitLab" {
|
} else if typ == "GitLab" {
|
||||||
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if isGothSupport(providerType) {
|
} else if typ == "Baidu" {
|
||||||
return NewGothIdProvider(providerType, clientId, clientSecret, redirectUrl)
|
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
|
} else if isGothSupport(typ) {
|
||||||
|
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
171
idp/wecom_internal.go
Normal file
171
idp/wecom_internal.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed 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 idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
//This idp is using wecom internal application api as idp
|
||||||
|
type WeComInternalIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWeComInternalIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComInternalIdProvider {
|
||||||
|
idp := &WeComInternalIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
idp.Config = config
|
||||||
|
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *WeComInternalIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *WeComInternalIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type WecomInterToken struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||||
|
// get more detail via: https://developer.work.weixin.qq.com/document/path/91039
|
||||||
|
func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
pTokenParams := &struct {
|
||||||
|
CorpId string `json:"corpid"`
|
||||||
|
Corpsecret string `json:"corpsecret"`
|
||||||
|
}{idp.Config.ClientID, idp.Config.ClientSecret}
|
||||||
|
resp, err := idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", pTokenParams.CorpId, pTokenParams.Corpsecret))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pToken := &WecomInterToken{}
|
||||||
|
err = json.Unmarshal(data, pToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pToken.Errcode != 0 {
|
||||||
|
return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", pToken.Errcode, pToken.Errmsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: pToken.AccessToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
raw["code"] = code
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type WecomInternalUserResp struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
UserId string `json:"UserId"`
|
||||||
|
OpenId string `json:"OpenId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WecomInternalUserInfo struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
OpenId string `json:"open_userid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
//Get userid first
|
||||||
|
accessToken := token.AccessToken
|
||||||
|
code := token.Extra("code").(string)
|
||||||
|
resp, err := idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s", accessToken, code))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userResp := &WecomInternalUserResp{}
|
||||||
|
err = json.Unmarshal(data, userResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if userResp.Errcode != 0 {
|
||||||
|
return nil, fmt.Errorf("userIdResp.Errcode = %d, userIdResp.Errmsg = %s", userResp.Errcode, userResp.Errmsg)
|
||||||
|
}
|
||||||
|
if userResp.OpenId != "" {
|
||||||
|
return nil, fmt.Errorf("not an internal user")
|
||||||
|
}
|
||||||
|
//Use userid and accesstoken to get user information
|
||||||
|
resp, err = idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s", accessToken, userResp.UserId))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err = io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
infoResp := &WecomInternalUserInfo{}
|
||||||
|
err = json.Unmarshal(data, infoResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if infoResp.Errcode != 0 {
|
||||||
|
return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
|
||||||
|
}
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: infoResp.OpenId,
|
||||||
|
Username: infoResp.Name,
|
||||||
|
DisplayName: infoResp.Name,
|
||||||
|
Email: infoResp.Email,
|
||||||
|
AvatarUrl: infoResp.Avatar,
|
||||||
|
}
|
||||||
|
|
||||||
|
if userInfo.Id == "" {
|
||||||
|
userInfo.Id = userInfo.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
56
k8s.yaml
Normal file
56
k8s.yaml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# this is only an EXAMPLE of deploying casddor in kubernetes
|
||||||
|
# please modify this file according to your requirements
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this field
|
||||||
|
#namespace: casdoor
|
||||||
|
name: casdoor-svc
|
||||||
|
labels:
|
||||||
|
app: casdoor
|
||||||
|
spec:
|
||||||
|
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this filed
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
selector:
|
||||||
|
app: casdoor
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this field
|
||||||
|
#namespace: casdoor
|
||||||
|
name: casdoor-deployment
|
||||||
|
labels:
|
||||||
|
app: casdoor
|
||||||
|
spec:
|
||||||
|
#EDIT IT: if you don't use redis, casdoor should not have multiple replicas
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: casdoor
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: casdoor
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: casdoor-container
|
||||||
|
image: casbin/casdoor:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8000
|
||||||
|
volumeMounts:
|
||||||
|
# the mounted directory path in THE CONTAINER
|
||||||
|
- mountPath: /conf
|
||||||
|
name: conf
|
||||||
|
env:
|
||||||
|
- name: RUNNING_IN_DOCKER
|
||||||
|
value: "true"
|
||||||
|
#if you want to deploy this in real prod env, consider the config map
|
||||||
|
volumes:
|
||||||
|
- name: conf
|
||||||
|
hostPath:
|
||||||
|
#EDIT IT: the mounted directory path in THE HOST
|
||||||
|
path: /conf
|
9
main.go
9
main.go
@@ -19,7 +19,6 @@ import (
|
|||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/plugins/cors"
|
|
||||||
_ "github.com/astaxie/beego/session/redis"
|
_ "github.com/astaxie/beego/session/redis"
|
||||||
"github.com/casdoor/casdoor/authz"
|
"github.com/casdoor/casdoor/authz"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -40,14 +39,6 @@ func main() {
|
|||||||
|
|
||||||
go object.RunSyncUsersJob()
|
go object.RunSyncUsersJob()
|
||||||
|
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
|
|
||||||
AllowOrigins: []string{"*"},
|
|
||||||
AllowMethods: []string{"GET", "PUT", "PATCH"},
|
|
||||||
AllowHeaders: []string{"Origin"},
|
|
||||||
ExposeHeaders: []string{"Content-Length"},
|
|
||||||
AllowCredentials: true,
|
|
||||||
}))
|
|
||||||
|
|
||||||
//beego.DelStaticPath("/static")
|
//beego.DelStaticPath("/static")
|
||||||
beego.SetStaticPath("/static", "web/build/static")
|
beego.SetStaticPath("/static", "web/build/static")
|
||||||
beego.BConfig.WebConfig.DirectoryIndex = true
|
beego.BConfig.WebConfig.DirectoryIndex = true
|
||||||
|
@@ -190,12 +190,17 @@ func (a *Adapter) createTable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
||||||
session := adapter.Engine.Limit(limit, offset).Where("1=1")
|
session := adapter.Engine.Prepare()
|
||||||
|
if offset != -1 && limit != -1 {
|
||||||
|
session.Limit(limit, offset)
|
||||||
|
}
|
||||||
if owner != "" {
|
if owner != "" {
|
||||||
session = session.And("owner=?", owner)
|
session = session.And("owner=?", owner)
|
||||||
}
|
}
|
||||||
if field != "" && value != "" {
|
if field != "" && value != "" {
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
if filterField(field) {
|
||||||
|
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if sortField == "" || sortOrder == "" {
|
if sortField == "" || sortOrder == "" {
|
||||||
sortField = "created_time"
|
sortField = "created_time"
|
||||||
@@ -206,4 +211,4 @@ func GetSession(owner string, offset, limit int, field, value, sortField, sortOr
|
|||||||
session = session.Desc(util.SnakeString(sortField))
|
session = session.Desc(util.SnakeString(sortField))
|
||||||
}
|
}
|
||||||
return session
|
return session
|
||||||
}
|
}
|
@@ -56,10 +56,7 @@ type Application struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetApplicationCount(owner, field, value string) int {
|
func GetApplicationCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Application{})
|
count, err := session.Count(&Application{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -53,10 +53,7 @@ func GetMaskedCerts(certs []*Cert) []*Cert {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetCertCount(owner, field, value string) int {
|
func GetCertCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Cert{})
|
count, err := session.Count(&Cert{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -23,10 +23,14 @@ import (
|
|||||||
goldap "github.com/go-ldap/ldap/v3"
|
goldap "github.com/go-ldap/ldap/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var reWhiteSpace *regexp.Regexp
|
var (
|
||||||
|
reWhiteSpace *regexp.Regexp
|
||||||
|
reFieldWhiteList *regexp.Regexp
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
reWhiteSpace, _ = regexp.Compile(`\s`)
|
reWhiteSpace, _ = regexp.Compile(`\s`)
|
||||||
|
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
|
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
|
||||||
@@ -179,3 +183,7 @@ func CheckUserPassword(organization string, username string, password string) (*
|
|||||||
|
|
||||||
return user, ""
|
return user, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterField(field string) bool {
|
||||||
|
return reFieldWhiteList.MatchString(field)
|
||||||
|
}
|
@@ -54,7 +54,7 @@ func init() {
|
|||||||
Issuer: origin,
|
Issuer: origin,
|
||||||
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", origin),
|
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", origin),
|
||||||
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", origin),
|
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", origin),
|
||||||
UserinfoEndpoint: fmt.Sprintf("%s/api/get-account", origin),
|
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", origin),
|
||||||
JwksUri: fmt.Sprintf("%s/api/certs", origin),
|
JwksUri: fmt.Sprintf("%s/api/certs", origin),
|
||||||
ResponseTypesSupported: []string{"id_token"},
|
ResponseTypesSupported: []string{"id_token"},
|
||||||
ResponseModesSupported: []string{"login", "code", "link"},
|
ResponseModesSupported: []string{"login", "code", "link"},
|
||||||
|
@@ -15,8 +15,6 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/cred"
|
"github.com/casdoor/casdoor/cred"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
@@ -39,10 +37,7 @@ type Organization struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) int {
|
func GetOrganizationCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Organization{})
|
count, err := session.Count(&Organization{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -39,10 +39,7 @@ type Permission struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetPermissionCount(owner, field, value string) int {
|
func GetPermissionCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Permission{})
|
count, err := session.Count(&Permission{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -29,6 +29,7 @@ type Provider struct {
|
|||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Category string `xorm:"varchar(100)" json:"category"`
|
Category string `xorm:"varchar(100)" json:"category"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
|
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||||
Method string `xorm:"varchar(100)" json:"method"`
|
Method string `xorm:"varchar(100)" json:"method"`
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||||
@@ -81,10 +82,7 @@ func GetMaskedProviders(providers []*Provider) []*Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetProviderCount(owner, field, value string) int {
|
func GetProviderCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Provider{})
|
count, err := session.Count(&Provider{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -102,10 +102,7 @@ func AddRecord(record *Record) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetRecordCount(field, value string) int {
|
func GetRecordCount(field, value string) int {
|
||||||
session := adapter.Engine.Where("1=1")
|
session := GetSession("", -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Record{})
|
count, err := session.Count(&Record{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -40,11 +40,8 @@ type Resource struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetResourceCount(owner, user, field, value string) int {
|
func GetResourceCount(owner, user, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=? and user=?", owner, user)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
count, err := session.Count(&Resource{User: user})
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Resource{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@@ -33,10 +33,7 @@ type Role struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetRoleCount(owner, field, value string) int {
|
func GetRoleCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Role{})
|
count, err := session.Count(&Role{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -56,10 +56,7 @@ type Syncer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetSyncerCount(owner, field, value string) int {
|
func GetSyncerCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Syncer{})
|
count, err := session.Count(&Syncer{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -19,6 +19,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
@@ -45,6 +46,8 @@ type Token struct {
|
|||||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||||
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
||||||
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
||||||
|
CodeIsUsed bool `json:"codeIsUsed"`
|
||||||
|
CodeExpireIn int64 `json:"codeExpireIn"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenWrapper struct {
|
type TokenWrapper struct {
|
||||||
@@ -57,10 +60,7 @@ type TokenWrapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetTokenCount(owner, field, value string) int {
|
func GetTokenCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Token{})
|
count, err := session.Count(&Token{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -109,8 +109,8 @@ func getToken(owner string, name string) *Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getTokenByCode(code string) *Token {
|
func getTokenByCode(code string) *Token {
|
||||||
token := Token{}
|
token := Token{Code: code}
|
||||||
existed, err := adapter.Engine.Where("code=?", code).Get(&token)
|
existed, err := adapter.Engine.Get(&token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -122,6 +122,15 @@ func getTokenByCode(code string) *Token {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateUsedByCode(token *Token) bool {
|
||||||
|
affected, err := adapter.Engine.Where("code=?", token.Code).Cols("code_is_used").Update(token)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
func GetToken(id string) *Token {
|
func GetToken(id string) *Token {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
return getToken(owner, name)
|
return getToken(owner, name)
|
||||||
@@ -159,6 +168,16 @@ func DeleteToken(token *Token) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTokenByAccessToken(accessToken string) *Token {
|
||||||
|
//Check if the accessToken is in the database
|
||||||
|
token := Token{AccessToken: accessToken}
|
||||||
|
existed, err := adapter.Engine.Get(&token)
|
||||||
|
if err != nil || !existed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &token
|
||||||
|
}
|
||||||
|
|
||||||
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
|
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
|
||||||
if responseType != "code" {
|
if responseType != "code" {
|
||||||
return "response_type should be \"code\"", nil
|
return "response_type should be \"code\"", nil
|
||||||
@@ -208,12 +227,12 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, refreshToken, err := generateJwtToken(application, user, nonce)
|
accessToken, refreshToken, err := generateJwtToken(application, user, nonce, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if challenge == "null"{
|
if challenge == "null" {
|
||||||
challenge = ""
|
challenge = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +250,8 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
Scope: scope,
|
Scope: scope,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
CodeChallenge: challenge,
|
CodeChallenge: challenge,
|
||||||
|
CodeIsUsed: false,
|
||||||
|
CodeExpireIn: time.Now().Add(time.Minute * 5).Unix(),
|
||||||
}
|
}
|
||||||
AddToken(token)
|
AddToken(token)
|
||||||
|
|
||||||
@@ -304,7 +325,29 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
Scope: "",
|
Scope: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if token.CodeIsUsed {
|
||||||
|
//Resist replay attacks, if the code is reused, the token generated with this code will be deleted
|
||||||
|
DeleteToken(token)
|
||||||
|
return &TokenWrapper{
|
||||||
|
AccessToken: "error: code has been used.",
|
||||||
|
TokenType: "",
|
||||||
|
ExpiresIn: 0,
|
||||||
|
Scope: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time.Now().Unix() > token.CodeExpireIn {
|
||||||
|
//can only use the code to generate a token within five minutes
|
||||||
|
DeleteToken(token)
|
||||||
|
return &TokenWrapper{
|
||||||
|
AccessToken: "error: code has expired",
|
||||||
|
TokenType: "",
|
||||||
|
ExpiresIn: 0,
|
||||||
|
Scope: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token.CodeIsUsed = true
|
||||||
|
updateUsedByCode(token)
|
||||||
tokenWrapper := &TokenWrapper{
|
tokenWrapper := &TokenWrapper{
|
||||||
AccessToken: token.AccessToken,
|
AccessToken: token.AccessToken,
|
||||||
IdToken: token.AccessToken,
|
IdToken: token.AccessToken,
|
||||||
@@ -376,7 +419,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
Scope: "",
|
Scope: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "")
|
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ type Claims struct {
|
|||||||
*User
|
*User
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ type UserShort struct {
|
|||||||
type ClaimsShort struct {
|
type ClaimsShort struct {
|
||||||
*UserShort
|
*UserShort
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,12 +55,13 @@ func getShortClaims(claims Claims) ClaimsShort {
|
|||||||
res := ClaimsShort{
|
res := ClaimsShort{
|
||||||
UserShort: getShortUser(claims.User),
|
UserShort: getShortUser(claims.User),
|
||||||
Nonce: claims.Nonce,
|
Nonce: claims.Nonce,
|
||||||
|
Scope: claims.Scope,
|
||||||
RegisteredClaims: claims.RegisteredClaims,
|
RegisteredClaims: claims.RegisteredClaims,
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateJwtToken(application *Application, user *User, nonce string) (string, string, error) {
|
func generateJwtToken(application *Application, user *User, nonce string, scope string) (string, string, error) {
|
||||||
nowTime := time.Now()
|
nowTime := time.Now()
|
||||||
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
||||||
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||||
@@ -69,7 +72,8 @@ func generateJwtToken(application *Application, user *User, nonce string) (strin
|
|||||||
User: user,
|
User: user,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
// FIXME: A workaround for custom claim by reusing `tag` in user info
|
// FIXME: A workaround for custom claim by reusing `tag` in user info
|
||||||
Tag: user.Tag,
|
Tag: user.Tag,
|
||||||
|
Scope: scope,
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
Issuer: beego.AppConfig.String("origin"),
|
Issuer: beego.AppConfig.String("origin"),
|
||||||
Subject: user.Id,
|
Subject: user.Id,
|
||||||
|
@@ -79,6 +79,7 @@ type User struct {
|
|||||||
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||||
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||||
|
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||||
@@ -88,10 +89,7 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetGlobalUserCount(field, value string) int {
|
func GetGlobalUserCount(field, value string) int {
|
||||||
session := adapter.Engine.Where("1=1")
|
session := GetSession("", -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&User{})
|
count, err := session.Count(&User{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -122,10 +120,7 @@ func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetUserCount(owner, field, value string) int {
|
func GetUserCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&User{})
|
count, err := session.Count(&User{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -43,10 +43,7 @@ type Webhook struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetWebhookCount(owner, field, value string) int {
|
func GetWebhookCount(owner, field, value string) int {
|
||||||
session := adapter.Engine.Where("owner=?", owner)
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
if field != "" && value != "" {
|
|
||||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
|
||||||
}
|
|
||||||
count, err := session.Count(&Webhook{})
|
count, err := session.Count(&Webhook{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -16,7 +16,6 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -28,21 +27,28 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
// return
|
// return
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// "/page?access_token=123"
|
// GET parameter like "/page?access_token=123" or
|
||||||
|
// HTTP Bearer token like "Authorization: Bearer 123"
|
||||||
accessToken := ctx.Input.Query("accessToken")
|
accessToken := ctx.Input.Query("accessToken")
|
||||||
|
if accessToken == "" {
|
||||||
|
accessToken = parseBearerToken(ctx)
|
||||||
|
}
|
||||||
if accessToken != "" {
|
if accessToken != "" {
|
||||||
cert := object.GetDefaultCert()
|
token := object.GetTokenByAccessToken(accessToken)
|
||||||
claims, err := object.ParseJwtToken(accessToken, cert)
|
if token == nil {
|
||||||
if err != nil {
|
responseError(ctx, "Access token doesn't exist")
|
||||||
responseError(ctx, "invalid JWT token")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if time.Now().Unix() > claims.ExpiresAt.Unix() {
|
|
||||||
responseError(ctx, "expired JWT token")
|
if !util.IsTokenExpired(token.CreatedTime, token.ExpiresIn) {
|
||||||
|
responseError(ctx, "Access token has expired")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", claims.User.Owner, claims.User.Name)
|
userId := fmt.Sprintf("%s/%s", token.Organization, token.User)
|
||||||
|
application, _ := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
||||||
setSessionUser(ctx, userId)
|
setSessionUser(ctx, userId)
|
||||||
|
setSessionOidc(ctx, token.Scope, application.ClientId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,18 +74,4 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Bearer token
|
|
||||||
// Authorization: Bearer bearerToken
|
|
||||||
bearerToken := parseBearerToken(ctx)
|
|
||||||
if bearerToken != "" {
|
|
||||||
cert := object.GetDefaultCert()
|
|
||||||
claims, err := object.ParseJwtToken(bearerToken, cert)
|
|
||||||
if err != nil {
|
|
||||||
responseError(ctx, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setSessionUser(ctx, fmt.Sprintf("%s/%s", claims.Owner, claims.Name))
|
|
||||||
setSessionExpire(ctx, claims.ExpiresAt.Unix())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -97,6 +97,18 @@ func setSessionExpire(ctx *context.Context, ExpireTime int64) {
|
|||||||
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setSessionOidc(ctx *context.Context, scope string, aud string) {
|
||||||
|
err := ctx.Input.CruSession.Set("scope", scope)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = ctx.Input.CruSession.Set("aud", aud)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
|
}
|
||||||
|
|
||||||
func parseBearerToken(ctx *context.Context) string {
|
func parseBearerToken(ctx *context.Context) string {
|
||||||
header := ctx.Request.Header.Get("Authorization")
|
header := ctx.Request.Header.Get("Authorization")
|
||||||
tokens := strings.Split(header, " ")
|
tokens := strings.Split(header, " ")
|
||||||
|
@@ -50,6 +50,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
|
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
|
||||||
beego.Router("/api/logout", &controllers.ApiController{}, "POST:Logout")
|
beego.Router("/api/logout", &controllers.ApiController{}, "POST:Logout")
|
||||||
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
|
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
|
||||||
|
beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo")
|
||||||
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
|
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
|
||||||
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
||||||
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
||||||
|
@@ -28,3 +28,9 @@ func GetCurrentTime() string {
|
|||||||
func GetCurrentUnixTime() string {
|
func GetCurrentUnixTime() string {
|
||||||
return strconv.FormatInt(time.Now().UnixNano(), 10)
|
return strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsTokenExpired(createdTime string, expiresIn int) bool {
|
||||||
|
createdTimeObj, _ := time.Parse(time.RFC3339, createdTime)
|
||||||
|
expiresAtObj := createdTimeObj.Add(time.Duration(expiresIn) * time.Minute)
|
||||||
|
return time.Now().Before(expiresAtObj)
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,14 @@
|
|||||||
const CracoLessPlugin = require('craco-less');
|
const CracoLessPlugin = require('craco-less');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
devServer: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
plugin: CracoLessPlugin,
|
plugin: CracoLessPlugin,
|
||||||
|
@@ -91,18 +91,21 @@ class ProviderEditPage extends React.Component {
|
|||||||
getAppIdRow() {
|
getAppIdRow() {
|
||||||
let text, tooltip;
|
let text, tooltip;
|
||||||
if (this.state.provider.category === "SMS" && this.state.provider.type === "Tencent Cloud SMS") {
|
if (this.state.provider.category === "SMS" && this.state.provider.type === "Tencent Cloud SMS") {
|
||||||
text = "provider:App ID";
|
text = i18next.t("provider:App ID");
|
||||||
tooltip = "provider:App ID - Tooltip";
|
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||||
|
} else if (this.state.provider.type === "WeCom" && this.state.provider.subType === "Internal") {
|
||||||
|
text = i18next.t("provider:Agent ID");
|
||||||
|
tooltip = i18next.t("provider:Agent ID - Tooltip");
|
||||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
|
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
|
||||||
text = "provider:SMS account";
|
text = i18next.t("provider:SMS account");
|
||||||
tooltip = "provider:SMS account - Tooltip";
|
tooltip = i18next.t("provider:SMS account - Tooltip");
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Row style={{marginTop: '20px'}} >
|
return <Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t(text), i18next.t(tooltip))} :
|
{Setting.getLabel(text, tooltip)} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.appId} onChange={e => {
|
<Input value={this.state.provider.appId} onChange={e => {
|
||||||
@@ -205,20 +208,36 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "WeCom" ? null : (
|
this.state.provider.type !== "WeCom" ? null : (
|
||||||
<Row style={{marginTop: '20px'}} >
|
<React.Fragment>
|
||||||
<Col style={{marginTop: '5px'}} span={2}>
|
<Row style={{marginTop: '20px'}} >
|
||||||
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
</Col>
|
{Setting.getLabel(i18next.t("provider:Sub type"), i18next.t("provider:Sub type - Tooltip"))} :
|
||||||
<Col span={22} >
|
</Col>
|
||||||
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.method} onChange={value => {
|
<Col span={22} >
|
||||||
this.updateProviderField('method', value);
|
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.subType} onChange={value => {
|
||||||
}}>
|
this.updateProviderField('subType', value);
|
||||||
{
|
}}>
|
||||||
[{name: "Normal"}, {name: "Silent"}].map((method, index) => <Option key={index} value={method.name}>{method.name}</Option>)
|
{
|
||||||
}
|
Setting.getProviderSubTypeOptions(this.state.provider.type).map((providerSubType, index) => <Option key={index} value={providerSubType.id}>{providerSubType.name}</Option>)
|
||||||
</Select>
|
}
|
||||||
</Col>
|
</Select>
|
||||||
</Row>
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.method} onChange={value => {
|
||||||
|
this.updateProviderField('method', value);
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
[{name: "Normal"}, {name: "Silent"}].map((method, index) => <Option key={index} value={method.name}>{method.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
@@ -42,10 +42,10 @@ export function getCountryRegionData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initServerUrl() {
|
export function initServerUrl() {
|
||||||
const hostname = window.location.hostname;
|
//const hostname = window.location.hostname;
|
||||||
if (hostname === "localhost") {
|
// if (hostname === "localhost") {
|
||||||
ServerUrl = `http://${hostname}:8000`;
|
// ServerUrl = `http://${hostname}:8000`;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLocalhost() {
|
export function isLocalhost() {
|
||||||
@@ -398,6 +398,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'WeCom', name: 'WeCom'},
|
{id: 'WeCom', name: 'WeCom'},
|
||||||
{id: 'Lark', name: 'Lark'},
|
{id: 'Lark', name: 'Lark'},
|
||||||
{id: 'GitLab', name: 'GitLab'},
|
{id: 'GitLab', name: 'GitLab'},
|
||||||
|
{id: 'Baidu', name: 'Baidu'},
|
||||||
{id: 'Apple', name: 'Apple'},
|
{id: 'Apple', name: 'Apple'},
|
||||||
{id: 'AzureAD', name: 'AzureAD'},
|
{id: 'AzureAD', name: 'AzureAD'},
|
||||||
{id: 'Slack', name: 'Slack'},
|
{id: 'Slack', name: 'Slack'},
|
||||||
@@ -436,6 +437,19 @@ export function getProviderTypeOptions(category) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProviderSubTypeOptions(type) {
|
||||||
|
if (type === "WeCom") {
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
{id: 'Internal', name: 'Internal'},
|
||||||
|
{id: 'Third-party', name: 'Third-party'},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function renderLogo(application) {
|
export function renderLogo(application) {
|
||||||
if (application === null) {
|
if (application === null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -595,6 +609,7 @@ export function getTagColor(s) {
|
|||||||
|
|
||||||
export function getTags(tags) {
|
export function getTags(tags) {
|
||||||
let res = [];
|
let res = [];
|
||||||
|
if (!tags) return res;
|
||||||
tags.forEach((tag, i) => {
|
tags.forEach((tag, i) => {
|
||||||
res.push(
|
res.push(
|
||||||
<Tag color={getTagColor(tag)}>
|
<Tag color={getTagColor(tag)}>
|
||||||
|
32
web/src/auth/BaiduLoginButton.js
Normal file
32
web/src/auth/BaiduLoginButton.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed 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.
|
||||||
|
|
||||||
|
import {createButton} from "react-social-login-buttons";
|
||||||
|
import {StaticBaseUrl} from "../Setting";
|
||||||
|
|
||||||
|
function Icon({ width = 24, height = 24, color }) {
|
||||||
|
return <img src={`${StaticBaseUrl}/buttons/baidu.svg`} alt="Sign in with Baidu" style={{width: 24, height: 24}} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
text: "Sign in with Baidu",
|
||||||
|
icon: Icon,
|
||||||
|
iconFormat: name => `fa fa-${name}`,
|
||||||
|
style: {background: "#ffffff", color: "#000000"},
|
||||||
|
activeStyle: {background: "#ededee"},
|
||||||
|
};
|
||||||
|
|
||||||
|
const BaiduLoginButton = createButton(config);
|
||||||
|
|
||||||
|
export default BaiduLoginButton;
|
@@ -34,6 +34,7 @@ import LinkedInLoginButton from "./LinkedInLoginButton";
|
|||||||
import WeComLoginButton from "./WeComLoginButton";
|
import WeComLoginButton from "./WeComLoginButton";
|
||||||
import LarkLoginButton from "./LarkLoginButton";
|
import LarkLoginButton from "./LarkLoginButton";
|
||||||
import GitLabLoginButton from "./GitLabLoginButton";
|
import GitLabLoginButton from "./GitLabLoginButton";
|
||||||
|
import BaiduLoginButton from "./BaiduLoginButton";
|
||||||
import AppleLoginButton from "./AppleLoginButton"
|
import AppleLoginButton from "./AppleLoginButton"
|
||||||
import AzureADLoginButton from "./AzureADLoginButton";
|
import AzureADLoginButton from "./AzureADLoginButton";
|
||||||
import SlackLoginButton from "./SlackLoginButton";
|
import SlackLoginButton from "./SlackLoginButton";
|
||||||
@@ -183,6 +184,8 @@ class LoginPage extends React.Component {
|
|||||||
return <LarkLoginButton text={text} align={"center"} />
|
return <LarkLoginButton text={text} align={"center"} />
|
||||||
} else if (type === "GitLab") {
|
} else if (type === "GitLab") {
|
||||||
return <GitLabLoginButton text={text} align={"center"} />
|
return <GitLabLoginButton text={text} align={"center"} />
|
||||||
|
} else if (type === "Baidu") {
|
||||||
|
return <BaiduLoginButton text={text} align={"center"} />
|
||||||
} else if (type === "Apple") {
|
} else if (type === "Apple") {
|
||||||
return <AppleLoginButton text={text} align={"center"} />
|
return <AppleLoginButton text={text} align={"center"} />
|
||||||
} else if (type === "AzureAD") {
|
} else if (type === "AzureAD") {
|
||||||
@@ -482,6 +485,19 @@ class LoginPage extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams(this.props.location.search);
|
||||||
|
let silentSignin = params.get("silentSignin");
|
||||||
|
if (silentSignin !== null) {
|
||||||
|
if (window !== window.parent) {
|
||||||
|
const message = {tag: "Casdoor", type: "SilentSignin", data: "signing-in"};
|
||||||
|
window.parent.postMessage(message, "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
let values = {};
|
||||||
|
values["application"] = this.state.application.name;
|
||||||
|
this.onFinish(values);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{fontSize: 16, textAlign: "left"}}>
|
<div style={{fontSize: 16, textAlign: "left"}}>
|
||||||
|
@@ -60,6 +60,7 @@ const authInfo = {
|
|||||||
scope: "snsapi_userinfo",
|
scope: "snsapi_userinfo",
|
||||||
endpoint: "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect",
|
endpoint: "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect",
|
||||||
silentEndpoint: "https://open.weixin.qq.com/connect/oauth2/authorize",
|
silentEndpoint: "https://open.weixin.qq.com/connect/oauth2/authorize",
|
||||||
|
internalEndpoint: "https://open.work.weixin.qq.com/wwopen/sso/qrConnect",
|
||||||
},
|
},
|
||||||
Lark: {
|
Lark: {
|
||||||
// scope: "email",
|
// scope: "email",
|
||||||
@@ -69,6 +70,10 @@ const authInfo = {
|
|||||||
scope: "read_user+profile",
|
scope: "read_user+profile",
|
||||||
endpoint: "https://gitlab.com/oauth/authorize",
|
endpoint: "https://gitlab.com/oauth/authorize",
|
||||||
},
|
},
|
||||||
|
Baidu: {
|
||||||
|
scope: "basic",
|
||||||
|
endpoint: "http://openapi.baidu.com/oauth/2.0/authorize",
|
||||||
|
},
|
||||||
Apple: {
|
Apple: {
|
||||||
scope: "name%20email",
|
scope: "name%20email",
|
||||||
endpoint: "https://appleid.apple.com/auth/authorize",
|
endpoint: "https://appleid.apple.com/auth/authorize",
|
||||||
@@ -188,7 +193,7 @@ export function getAuthUrl(application, provider, method) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint = authInfo[provider.type].endpoint;
|
let endpoint = authInfo[provider.type].endpoint;
|
||||||
const redirectUri = `${window.location.origin}/callback`;
|
const redirectUri = `${window.location.origin}/callback`;
|
||||||
const scope = authInfo[provider.type].scope;
|
const scope = authInfo[provider.type].scope;
|
||||||
const state = Util.getQueryParamsToState(application.name, provider.name, method);
|
const state = Util.getQueryParamsToState(application.name, provider.name, method);
|
||||||
@@ -216,17 +221,34 @@ export function getAuthUrl(application, provider, method) {
|
|||||||
} else if (provider.type === "LinkedIn") {
|
} else if (provider.type === "LinkedIn") {
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
||||||
} else if (provider.type === "WeCom") {
|
} else if (provider.type === "WeCom") {
|
||||||
if (provider.method === "Silent") {
|
if (provider.subType === "Internal") {
|
||||||
return `${authInfo[provider.type].silentEndpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&scope=${scope}&response_type=code#wechat_redirect`;
|
if (provider.method === "Silent") {
|
||||||
} else if (provider.method === "Normal") {
|
endpoint = authInfo[provider.type].silentEndpoint;
|
||||||
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&usertype=member`;
|
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&scope=${scope}&response_type=code#wechat_redirect`;
|
||||||
|
} else if (provider.method === "Normal") {
|
||||||
|
endpoint = authInfo[provider.type].internalEndpoint;
|
||||||
|
return `${endpoint}?appid=${provider.clientId}&agentid=${provider.appId}&redirect_uri=${redirectUri}&state=${state}&usertype=member`;
|
||||||
|
} else {
|
||||||
|
return `https://error:not-supported-provider-method:${provider.method}`;
|
||||||
|
}
|
||||||
|
} else if (provider.subType === "Third-party") {
|
||||||
|
if (provider.method === "Silent") {
|
||||||
|
endpoint = authInfo[provider.type].silentEndpoint;
|
||||||
|
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&scope=${scope}&response_type=code#wechat_redirect`;
|
||||||
|
} else if (provider.method === "Normal") {
|
||||||
|
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&usertype=member`;
|
||||||
|
} else {
|
||||||
|
return `https://error:not-supported-provider-method:${provider.method}`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return `https://error:not-supported-provider-method:${provider.method}`;
|
return `https://error:not-supported-provider-sub-type:${provider.subType}`;
|
||||||
}
|
}
|
||||||
} else if (provider.type === "Lark") {
|
} else if (provider.type === "Lark") {
|
||||||
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}`;
|
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}`;
|
||||||
} else if (provider.type === "GitLab") {
|
} else if (provider.type === "GitLab") {
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
|
} else if (provider.type === "Baidu") {
|
||||||
|
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&display=popup`;
|
||||||
} else if (provider.type === "Apple") {
|
} else if (provider.type === "Apple") {
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&response_mode=form_post`;
|
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&response_mode=form_post`;
|
||||||
} else if (provider.type === "AzureAD") {
|
} else if (provider.type === "AzureAD") {
|
||||||
|
@@ -247,6 +247,10 @@
|
|||||||
"provider": {
|
"provider": {
|
||||||
"Access key": "Zugangsschlüssel",
|
"Access key": "Zugangsschlüssel",
|
||||||
"Access key - Tooltip": "Zugriffsschlüssel - Tooltip",
|
"Access key - Tooltip": "Zugriffsschlüssel - Tooltip",
|
||||||
|
"Agent ID": "Agent ID",
|
||||||
|
"Agent ID - Tooltip": "Agent ID - Tooltip",
|
||||||
|
"App ID": "App ID",
|
||||||
|
"App ID - Tooltip": "App ID - Tooltip",
|
||||||
"Bucket": "Eimer",
|
"Bucket": "Eimer",
|
||||||
"Bucket - Tooltip": "Storage bucket name",
|
"Bucket - Tooltip": "Storage bucket name",
|
||||||
"Can not parse Metadata": "Metadaten können nicht analysiert werden",
|
"Can not parse Metadata": "Metadaten können nicht analysiert werden",
|
||||||
@@ -293,6 +297,8 @@
|
|||||||
"Region endpoint for Internet": "Region Endpunkt für Internet",
|
"Region endpoint for Internet": "Region Endpunkt für Internet",
|
||||||
"Region endpoint for Intranet": "Region Endpunkt für Intranet",
|
"Region endpoint for Intranet": "Region Endpunkt für Intranet",
|
||||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||||
|
"SMS account": "SMS account",
|
||||||
|
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||||
"SP ACS URL": "SP-ACS-URL",
|
"SP ACS URL": "SP-ACS-URL",
|
||||||
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
|
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
|
||||||
"SP Entity ID": "SP Entity ID",
|
"SP Entity ID": "SP Entity ID",
|
||||||
@@ -308,6 +314,8 @@
|
|||||||
"Signup HTML": "HTML registrieren",
|
"Signup HTML": "HTML registrieren",
|
||||||
"Signup HTML - Edit": "HTML registrieren - Bearbeiten",
|
"Signup HTML - Edit": "HTML registrieren - Bearbeiten",
|
||||||
"Signup HTML - Tooltip": "HTML registrieren - Tooltip",
|
"Signup HTML - Tooltip": "HTML registrieren - Tooltip",
|
||||||
|
"Sub type": "Sub type",
|
||||||
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Vorlagencode",
|
"Template Code": "Vorlagencode",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "Nutzungsbedingungen",
|
"Terms of Use": "Nutzungsbedingungen",
|
||||||
|
@@ -247,6 +247,10 @@
|
|||||||
"provider": {
|
"provider": {
|
||||||
"Access key": "Access key",
|
"Access key": "Access key",
|
||||||
"Access key - Tooltip": "Access key - Tooltip",
|
"Access key - Tooltip": "Access key - Tooltip",
|
||||||
|
"Agent ID": "Agent ID",
|
||||||
|
"Agent ID - Tooltip": "Agent ID - Tooltip",
|
||||||
|
"App ID": "App ID",
|
||||||
|
"App ID - Tooltip": "App ID - Tooltip",
|
||||||
"Bucket": "Bucket",
|
"Bucket": "Bucket",
|
||||||
"Bucket - Tooltip": "Bucket - Tooltip",
|
"Bucket - Tooltip": "Bucket - Tooltip",
|
||||||
"Can not parse Metadata": "Can not parse Metadata",
|
"Can not parse Metadata": "Can not parse Metadata",
|
||||||
@@ -293,6 +297,8 @@
|
|||||||
"Region endpoint for Internet": "Region endpoint for Internet",
|
"Region endpoint for Internet": "Region endpoint for Internet",
|
||||||
"Region endpoint for Intranet": "Region endpoint for Intranet",
|
"Region endpoint for Intranet": "Region endpoint for Intranet",
|
||||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||||
|
"SMS account": "SMS account",
|
||||||
|
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||||
"SP ACS URL": "SP ACS URL",
|
"SP ACS URL": "SP ACS URL",
|
||||||
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
|
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
|
||||||
"SP Entity ID": "SP Entity ID",
|
"SP Entity ID": "SP Entity ID",
|
||||||
@@ -308,6 +314,8 @@
|
|||||||
"Signup HTML": "Signup HTML",
|
"Signup HTML": "Signup HTML",
|
||||||
"Signup HTML - Edit": "Signup HTML - Edit",
|
"Signup HTML - Edit": "Signup HTML - Edit",
|
||||||
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
||||||
|
"Sub type": "Sub type",
|
||||||
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Template Code",
|
"Template Code": "Template Code",
|
||||||
"Template Code - Tooltip": "Template Code - Tooltip",
|
"Template Code - Tooltip": "Template Code - Tooltip",
|
||||||
"Terms of Use": "Terms of Use",
|
"Terms of Use": "Terms of Use",
|
||||||
|
@@ -247,6 +247,10 @@
|
|||||||
"provider": {
|
"provider": {
|
||||||
"Access key": "Clé d'accès",
|
"Access key": "Clé d'accès",
|
||||||
"Access key - Tooltip": "Touche d'accès - Infobulle",
|
"Access key - Tooltip": "Touche d'accès - Infobulle",
|
||||||
|
"Agent ID": "Agent ID",
|
||||||
|
"Agent ID - Tooltip": "Agent ID - Tooltip",
|
||||||
|
"App ID": "App ID",
|
||||||
|
"App ID - Tooltip": "App ID - Tooltip",
|
||||||
"Bucket": "Seau",
|
"Bucket": "Seau",
|
||||||
"Bucket - Tooltip": "Storage bucket name",
|
"Bucket - Tooltip": "Storage bucket name",
|
||||||
"Can not parse Metadata": "Impossible d'analyser les métadonnées",
|
"Can not parse Metadata": "Impossible d'analyser les métadonnées",
|
||||||
@@ -293,6 +297,8 @@
|
|||||||
"Region endpoint for Internet": "Point de terminaison de la région pour Internet",
|
"Region endpoint for Internet": "Point de terminaison de la région pour Internet",
|
||||||
"Region endpoint for Intranet": "Point de terminaison de la région pour Intranet",
|
"Region endpoint for Intranet": "Point de terminaison de la région pour Intranet",
|
||||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||||
|
"SMS account": "SMS account",
|
||||||
|
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||||
"SP ACS URL": "URL du SP ACS",
|
"SP ACS URL": "URL du SP ACS",
|
||||||
"SP ACS URL - Tooltip": "URL SP ACS - infobulle",
|
"SP ACS URL - Tooltip": "URL SP ACS - infobulle",
|
||||||
"SP Entity ID": "ID de l'entité SP",
|
"SP Entity ID": "ID de l'entité SP",
|
||||||
@@ -308,6 +314,8 @@
|
|||||||
"Signup HTML": "Inscription HTML",
|
"Signup HTML": "Inscription HTML",
|
||||||
"Signup HTML - Edit": "Inscription HTML - Modifier",
|
"Signup HTML - Edit": "Inscription HTML - Modifier",
|
||||||
"Signup HTML - Tooltip": "Inscription HTML - infobulle",
|
"Signup HTML - Tooltip": "Inscription HTML - infobulle",
|
||||||
|
"Sub type": "Sub type",
|
||||||
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Code du modèle",
|
"Template Code": "Code du modèle",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "Conditions d'utilisation",
|
"Terms of Use": "Conditions d'utilisation",
|
||||||
|
@@ -247,6 +247,10 @@
|
|||||||
"provider": {
|
"provider": {
|
||||||
"Access key": "アクセスキー",
|
"Access key": "アクセスキー",
|
||||||
"Access key - Tooltip": "アクセスキー → ツールチップ",
|
"Access key - Tooltip": "アクセスキー → ツールチップ",
|
||||||
|
"Agent ID": "Agent ID",
|
||||||
|
"Agent ID - Tooltip": "Agent ID - Tooltip",
|
||||||
|
"App ID": "App ID",
|
||||||
|
"App ID - Tooltip": "App ID - Tooltip",
|
||||||
"Bucket": "バケツ入りバケツ",
|
"Bucket": "バケツ入りバケツ",
|
||||||
"Bucket - Tooltip": "Storage bucket name",
|
"Bucket - Tooltip": "Storage bucket name",
|
||||||
"Can not parse Metadata": "メタデータをパースできません",
|
"Can not parse Metadata": "メタデータをパースできません",
|
||||||
@@ -293,6 +297,8 @@
|
|||||||
"Region endpoint for Internet": "インターネットのリージョンエンドポイント",
|
"Region endpoint for Internet": "インターネットのリージョンエンドポイント",
|
||||||
"Region endpoint for Intranet": "イントラネットのリージョンエンドポイント",
|
"Region endpoint for Intranet": "イントラネットのリージョンエンドポイント",
|
||||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||||
|
"SMS account": "SMS account",
|
||||||
|
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||||
"SP ACS URL": "SP ACS URL",
|
"SP ACS URL": "SP ACS URL",
|
||||||
"SP ACS URL - Tooltip": "SP ACS URL - ツールチップ",
|
"SP ACS URL - Tooltip": "SP ACS URL - ツールチップ",
|
||||||
"SP Entity ID": "SP ID",
|
"SP Entity ID": "SP ID",
|
||||||
@@ -308,6 +314,8 @@
|
|||||||
"Signup HTML": "HTMLの登録",
|
"Signup HTML": "HTMLの登録",
|
||||||
"Signup HTML - Edit": "HTMLの登録 - 編集",
|
"Signup HTML - Edit": "HTMLの登録 - 編集",
|
||||||
"Signup HTML - Tooltip": "サインアップ HTML - ツールチップ",
|
"Signup HTML - Tooltip": "サインアップ HTML - ツールチップ",
|
||||||
|
"Sub type": "Sub type",
|
||||||
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "テンプレートコード",
|
"Template Code": "テンプレートコード",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "利用規約",
|
"Terms of Use": "利用規約",
|
||||||
|
@@ -247,6 +247,10 @@
|
|||||||
"provider": {
|
"provider": {
|
||||||
"Access key": "Access key",
|
"Access key": "Access key",
|
||||||
"Access key - Tooltip": "Access key - Tooltip",
|
"Access key - Tooltip": "Access key - Tooltip",
|
||||||
|
"Agent ID": "Agent ID",
|
||||||
|
"Agent ID - Tooltip": "Agent ID - Tooltip",
|
||||||
|
"App ID": "App ID",
|
||||||
|
"App ID - Tooltip": "App ID - Tooltip",
|
||||||
"Bucket": "Bucket",
|
"Bucket": "Bucket",
|
||||||
"Bucket - Tooltip": "Storage bucket name",
|
"Bucket - Tooltip": "Storage bucket name",
|
||||||
"Can not parse Metadata": "Can not parse Metadata",
|
"Can not parse Metadata": "Can not parse Metadata",
|
||||||
@@ -293,6 +297,8 @@
|
|||||||
"Region endpoint for Internet": "Region endpoint for Internet",
|
"Region endpoint for Internet": "Region endpoint for Internet",
|
||||||
"Region endpoint for Intranet": "Region endpoint for Intranet",
|
"Region endpoint for Intranet": "Region endpoint for Intranet",
|
||||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||||
|
"SMS account": "SMS account",
|
||||||
|
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||||
"SP ACS URL": "SP ACS URL",
|
"SP ACS URL": "SP ACS URL",
|
||||||
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
|
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
|
||||||
"SP Entity ID": "SP Entity ID",
|
"SP Entity ID": "SP Entity ID",
|
||||||
@@ -308,6 +314,8 @@
|
|||||||
"Signup HTML": "Signup HTML",
|
"Signup HTML": "Signup HTML",
|
||||||
"Signup HTML - Edit": "Signup HTML - Edit",
|
"Signup HTML - Edit": "Signup HTML - Edit",
|
||||||
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
"Signup HTML - Tooltip": "Signup HTML - Tooltip",
|
||||||
|
"Sub type": "Sub type",
|
||||||
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Template Code",
|
"Template Code": "Template Code",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "Terms of Use",
|
"Terms of Use": "Terms of Use",
|
||||||
|
@@ -247,6 +247,10 @@
|
|||||||
"provider": {
|
"provider": {
|
||||||
"Access key": "Ключ доступа",
|
"Access key": "Ключ доступа",
|
||||||
"Access key - Tooltip": "Ключ доступа - Подсказка",
|
"Access key - Tooltip": "Ключ доступа - Подсказка",
|
||||||
|
"Agent ID": "Agent ID",
|
||||||
|
"Agent ID - Tooltip": "Agent ID - Tooltip",
|
||||||
|
"App ID": "App ID",
|
||||||
|
"App ID - Tooltip": "App ID - Tooltip",
|
||||||
"Bucket": "Ведро",
|
"Bucket": "Ведро",
|
||||||
"Bucket - Tooltip": "Storage bucket name",
|
"Bucket - Tooltip": "Storage bucket name",
|
||||||
"Can not parse Metadata": "Невозможно разобрать метаданные",
|
"Can not parse Metadata": "Невозможно разобрать метаданные",
|
||||||
@@ -293,6 +297,8 @@
|
|||||||
"Region endpoint for Internet": "Конечная точка региона для Интернета",
|
"Region endpoint for Internet": "Конечная точка региона для Интернета",
|
||||||
"Region endpoint for Intranet": "Конечная точка региона Интранета",
|
"Region endpoint for Intranet": "Конечная точка региона Интранета",
|
||||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||||
|
"SMS account": "SMS account",
|
||||||
|
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||||
"SP ACS URL": "SP ACS URL",
|
"SP ACS URL": "SP ACS URL",
|
||||||
"SP ACS URL - Tooltip": "SP ACS URL - Подсказка",
|
"SP ACS URL - Tooltip": "SP ACS URL - Подсказка",
|
||||||
"SP Entity ID": "Идентификатор сущности SP",
|
"SP Entity ID": "Идентификатор сущности SP",
|
||||||
@@ -308,6 +314,8 @@
|
|||||||
"Signup HTML": "Регистрация HTML",
|
"Signup HTML": "Регистрация HTML",
|
||||||
"Signup HTML - Edit": "Регистрация HTML - Редактировать",
|
"Signup HTML - Edit": "Регистрация HTML - Редактировать",
|
||||||
"Signup HTML - Tooltip": "Регистрация HTML - Подсказка",
|
"Signup HTML - Tooltip": "Регистрация HTML - Подсказка",
|
||||||
|
"Sub type": "Sub type",
|
||||||
|
"Sub type - Tooltip": "Sub type - Tooltip",
|
||||||
"Template Code": "Код шаблона",
|
"Template Code": "Код шаблона",
|
||||||
"Template Code - Tooltip": "Unique string-style identifier",
|
"Template Code - Tooltip": "Unique string-style identifier",
|
||||||
"Terms of Use": "Условия использования",
|
"Terms of Use": "Условия использования",
|
||||||
|
@@ -247,6 +247,10 @@
|
|||||||
"provider": {
|
"provider": {
|
||||||
"Access key": "访问密钥",
|
"Access key": "访问密钥",
|
||||||
"Access key - Tooltip": "Access key",
|
"Access key - Tooltip": "Access key",
|
||||||
|
"Agent ID": "Agent ID",
|
||||||
|
"Agent ID - Tooltip": "Agent ID - Tooltip",
|
||||||
|
"App ID": "App ID",
|
||||||
|
"App ID - Tooltip": "App ID - Tooltip",
|
||||||
"Bucket": "存储桶",
|
"Bucket": "存储桶",
|
||||||
"Bucket - Tooltip": "Bucket名称",
|
"Bucket - Tooltip": "Bucket名称",
|
||||||
"Can not parse Metadata": "无法解析元数据",
|
"Can not parse Metadata": "无法解析元数据",
|
||||||
@@ -293,6 +297,8 @@
|
|||||||
"Region endpoint for Internet": "地域节点 (外网)",
|
"Region endpoint for Internet": "地域节点 (外网)",
|
||||||
"Region endpoint for Intranet": "地域节点 (内网)",
|
"Region endpoint for Intranet": "地域节点 (内网)",
|
||||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||||
|
"SMS account": "SMS account",
|
||||||
|
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||||
"SP ACS URL": "SP ACS URL",
|
"SP ACS URL": "SP ACS URL",
|
||||||
"SP ACS URL - Tooltip": "SP ACS URL - 工具提示",
|
"SP ACS URL - Tooltip": "SP ACS URL - 工具提示",
|
||||||
"SP Entity ID": "SP 实体 ID",
|
"SP Entity ID": "SP 实体 ID",
|
||||||
@@ -308,6 +314,8 @@
|
|||||||
"Signup HTML": "注册 HTML",
|
"Signup HTML": "注册 HTML",
|
||||||
"Signup HTML - Edit": "注册 HTML - 编辑",
|
"Signup HTML - Edit": "注册 HTML - 编辑",
|
||||||
"Signup HTML - Tooltip": "注册 HTML - 工具提示",
|
"Signup HTML - Tooltip": "注册 HTML - 工具提示",
|
||||||
|
"Sub type": "子类型",
|
||||||
|
"Sub type - Tooltip": "子类型",
|
||||||
"Template Code": "模板CODE",
|
"Template Code": "模板CODE",
|
||||||
"Template Code - Tooltip": "模板CODE",
|
"Template Code - Tooltip": "模板CODE",
|
||||||
"Terms of Use": "使用条款",
|
"Terms of Use": "使用条款",
|
||||||
|
Reference in New Issue
Block a user