mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-22 03:43:49 +08:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
87506b84e3 | |||
fed9332246 | |||
33afc52a0b | |||
9035ca365a | |||
b97ae72179 | |||
9190db1099 | |||
1173f75794 | |||
086859d1ce | |||
9afaf5d695 | |||
521f90a603 | |||
4260efcfd0 | |||
d772b0b7a8 | |||
702b390da1 | |||
b15b3b9335 | |||
f8f864c5b9 | |||
90e790f83c | |||
58413246f3 | |||
8f307dd907 | |||
fe42b5e0ba | |||
383bf44391 | |||
36f5de3203 | |||
eae69c41d7 | |||
91057f54f3 |
@ -47,6 +47,7 @@ p, *, *, GET, /api/get-app-login, *, *
|
|||||||
p, *, *, POST, /api/logout, *, *
|
p, *, *, POST, /api/logout, *, *
|
||||||
p, *, *, GET, /api/logout, *, *
|
p, *, *, GET, /api/logout, *, *
|
||||||
p, *, *, POST, /api/callback, *, *
|
p, *, *, POST, /api/callback, *, *
|
||||||
|
p, *, *, POST, /api/device-auth, *, *
|
||||||
p, *, *, GET, /api/get-account, *, *
|
p, *, *, GET, /api/get-account, *, *
|
||||||
p, *, *, GET, /api/userinfo, *, *
|
p, *, *, GET, /api/userinfo, *, *
|
||||||
p, *, *, GET, /api/user, *, *
|
p, *, *, GET, /api/user, *, *
|
||||||
|
@ -31,7 +31,7 @@ radiusServerPort = 1812
|
|||||||
radiusDefaultOrganization = "built-in"
|
radiusDefaultOrganization = "built-in"
|
||||||
radiusSecret = "secret"
|
radiusSecret = "secret"
|
||||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||||
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||||
initDataNewOnly = false
|
initDataNewOnly = false
|
||||||
initDataFile = "./init_data.json"
|
initDataFile = "./init_data.json"
|
||||||
frontendBaseDir = "../cc_0"
|
frontendBaseDir = "../cc_0"
|
@ -115,7 +115,7 @@ func TestGetConfigLogs(t *testing.T) {
|
|||||||
description string
|
description string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"Default log config", `{"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}`},
|
{"Default log config", `{"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := beego.LoadAppConfig("ini", "app.conf")
|
err := beego.LoadAppConfig("ini", "app.conf")
|
||||||
|
@ -32,6 +32,7 @@ const (
|
|||||||
ResponseTypeIdToken = "id_token"
|
ResponseTypeIdToken = "id_token"
|
||||||
ResponseTypeSaml = "saml"
|
ResponseTypeSaml = "saml"
|
||||||
ResponseTypeCas = "cas"
|
ResponseTypeCas = "cas"
|
||||||
|
ResponseTypeDevice = "device"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/captcha"
|
"github.com/casdoor/casdoor/captcha"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@ -146,7 +147,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
c.ResponseError(c.T("auth:Challenge method should be S256"))
|
c.ResponseError(c.T("auth:Challenge method should be S256"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
code, err := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, c.Ctx.Request.Host, c.GetAcceptLanguage())
|
code, err := object.GetOAuthCode(userId, clientId, form.Provider, responseType, redirectUri, scope, state, nonce, codeChallenge, c.Ctx.Request.Host, c.GetAcceptLanguage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error(), nil)
|
c.ResponseError(err.Error(), nil)
|
||||||
return
|
return
|
||||||
@ -169,6 +170,32 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
|
|
||||||
resp.Data2 = user.NeedUpdatePassword
|
resp.Data2 = user.NeedUpdatePassword
|
||||||
}
|
}
|
||||||
|
} else if form.Type == ResponseTypeDevice {
|
||||||
|
authCache, ok := object.DeviceAuthMap.LoadAndDelete(form.UserCode)
|
||||||
|
if !ok {
|
||||||
|
c.ResponseError(c.T("auth:UserCode Expired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
authCacheCast := authCache.(object.DeviceAuthCache)
|
||||||
|
if authCacheCast.RequestAt.Add(time.Second * 120).Before(time.Now()) {
|
||||||
|
c.ResponseError(c.T("auth:UserCode Expired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCacheDeviceCode, ok := object.DeviceAuthMap.Load(authCacheCast.UserName)
|
||||||
|
if !ok {
|
||||||
|
c.ResponseError(c.T("auth:DeviceCode Invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCacheDeviceCodeCast := deviceAuthCacheDeviceCode.(object.DeviceAuthCache)
|
||||||
|
deviceAuthCacheDeviceCodeCast.UserName = user.Name
|
||||||
|
deviceAuthCacheDeviceCodeCast.UserSignIn = true
|
||||||
|
|
||||||
|
object.DeviceAuthMap.Store(authCacheCast.UserName, deviceAuthCacheDeviceCodeCast)
|
||||||
|
|
||||||
|
resp = &Response{Status: "ok", Msg: "", Data: userId, Data2: user.NeedUpdatePassword}
|
||||||
} else if form.Type == ResponseTypeSaml { // saml flow
|
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||||
res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
|
res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -242,6 +269,7 @@ func (c *ApiController) GetApplicationLogin() {
|
|||||||
state := c.Input().Get("state")
|
state := c.Input().Get("state")
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
loginType := c.Input().Get("type")
|
loginType := c.Input().Get("type")
|
||||||
|
userCode := c.Input().Get("userCode")
|
||||||
|
|
||||||
var application *object.Application
|
var application *object.Application
|
||||||
var msg string
|
var msg string
|
||||||
@ -268,6 +296,19 @@ func (c *ApiController) GetApplicationLogin() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else if loginType == "device" {
|
||||||
|
deviceAuthCache, ok := object.DeviceAuthMap.Load(userCode)
|
||||||
|
if !ok {
|
||||||
|
c.ResponseError(c.T("auth:UserCode Invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCacheCast := deviceAuthCache.(object.DeviceAuthCache)
|
||||||
|
application, err = object.GetApplication(deviceAuthCacheCast.ApplicationId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
@ -1215,3 +1256,75 @@ func (c *ApiController) Callback() {
|
|||||||
frontendCallbackUrl := fmt.Sprintf("/callback?code=%s&state=%s", code, state)
|
frontendCallbackUrl := fmt.Sprintf("/callback?code=%s&state=%s", code, state)
|
||||||
c.Ctx.Redirect(http.StatusFound, frontendCallbackUrl)
|
c.Ctx.Redirect(http.StatusFound, frontendCallbackUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeviceAuth
|
||||||
|
// @Title DeviceAuth
|
||||||
|
// @Tag Device Authorization Endpoint
|
||||||
|
// @Description Endpoint for the device authorization flow
|
||||||
|
// @router /device-auth [post]
|
||||||
|
// @Success 200 {object} object.DeviceAuthResponse The Response object
|
||||||
|
func (c *ApiController) DeviceAuth() {
|
||||||
|
clientId := c.Input().Get("client_id")
|
||||||
|
scope := c.Input().Get("scope")
|
||||||
|
application, err := object.GetApplicationByClientId(clientId)
|
||||||
|
if err != nil {
|
||||||
|
c.Data["json"] = object.TokenError{
|
||||||
|
Error: err.Error(),
|
||||||
|
ErrorDescription: err.Error(),
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if application == nil {
|
||||||
|
c.Data["json"] = object.TokenError{
|
||||||
|
Error: c.T("token:Invalid client_id"),
|
||||||
|
ErrorDescription: c.T("token:Invalid client_id"),
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceCode := util.GenerateId()
|
||||||
|
userCode := util.GetRandomName()
|
||||||
|
|
||||||
|
generateTime := 0
|
||||||
|
for {
|
||||||
|
if generateTime > 5 {
|
||||||
|
c.Data["json"] = object.TokenError{
|
||||||
|
Error: "userCode gen",
|
||||||
|
ErrorDescription: c.T("token:Invalid client_id"),
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, ok := object.DeviceAuthMap.Load(userCode)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTime++
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCache := object.DeviceAuthCache{
|
||||||
|
UserSignIn: false,
|
||||||
|
UserName: "",
|
||||||
|
Scope: scope,
|
||||||
|
ApplicationId: application.GetId(),
|
||||||
|
RequestAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
userAuthCache := object.DeviceAuthCache{
|
||||||
|
UserSignIn: false,
|
||||||
|
UserName: deviceCode,
|
||||||
|
Scope: scope,
|
||||||
|
ApplicationId: application.GetId(),
|
||||||
|
RequestAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
object.DeviceAuthMap.Store(deviceCode, deviceAuthCache)
|
||||||
|
object.DeviceAuthMap.Store(userCode, userAuthCache)
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetDeviceAuthResponse(deviceCode, userCode, c.Ctx.Request.Host)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -170,12 +171,13 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
tag := c.Input().Get("tag")
|
tag := c.Input().Get("tag")
|
||||||
avatar := c.Input().Get("avatar")
|
avatar := c.Input().Get("avatar")
|
||||||
refreshToken := c.Input().Get("refresh_token")
|
refreshToken := c.Input().Get("refresh_token")
|
||||||
|
deviceCode := c.Input().Get("device_code")
|
||||||
|
|
||||||
if clientId == "" && clientSecret == "" {
|
if clientId == "" && clientSecret == "" {
|
||||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Ctx.Input.RequestBody) != 0 {
|
if len(c.Ctx.Input.RequestBody) != 0 && grantType != "urn:ietf:params:oauth:grant-type:device_code" {
|
||||||
// If clientId is empty, try to read data from RequestBody
|
// If clientId is empty, try to read data from RequestBody
|
||||||
var tokenRequest TokenRequest
|
var tokenRequest TokenRequest
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
|
||||||
@ -219,6 +221,46 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if deviceCode != "" {
|
||||||
|
deviceAuthCache, ok := object.DeviceAuthMap.Load(deviceCode)
|
||||||
|
if !ok {
|
||||||
|
c.Data["json"] = &object.TokenError{
|
||||||
|
Error: "expired_token",
|
||||||
|
ErrorDescription: "token is expired",
|
||||||
|
}
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
c.ServeJSON()
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCacheCast := deviceAuthCache.(object.DeviceAuthCache)
|
||||||
|
if !deviceAuthCacheCast.UserSignIn {
|
||||||
|
c.Data["json"] = &object.TokenError{
|
||||||
|
Error: "authorization_pending",
|
||||||
|
ErrorDescription: "authorization pending",
|
||||||
|
}
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
c.ServeJSON()
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if deviceAuthCacheCast.RequestAt.Add(time.Second * 120).Before(time.Now()) {
|
||||||
|
c.Data["json"] = &object.TokenError{
|
||||||
|
Error: "expired_token",
|
||||||
|
ErrorDescription: "token is expired",
|
||||||
|
}
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
c.ServeJSON()
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
object.DeviceAuthMap.Delete(deviceCode)
|
||||||
|
|
||||||
|
username = deviceAuthCacheCast.UserName
|
||||||
|
}
|
||||||
|
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -34,6 +34,8 @@ func GetCredManager(passwordType string) CredManager {
|
|||||||
return NewPbkdf2SaltCredManager()
|
return NewPbkdf2SaltCredManager()
|
||||||
} else if passwordType == "argon2id" {
|
} else if passwordType == "argon2id" {
|
||||||
return NewArgon2idCredManager()
|
return NewArgon2idCredManager()
|
||||||
|
} else if passwordType == "pbkdf2-django" {
|
||||||
|
return NewPbkdf2DjangoCredManager()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
71
cred/pbkdf2_django.go
Normal file
71
cred/pbkdf2_django.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2025 The Casdoor 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 cred
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// password type: pbkdf2-django
|
||||||
|
|
||||||
|
type Pbkdf2DjangoCredManager struct{}
|
||||||
|
|
||||||
|
func NewPbkdf2DjangoCredManager() *Pbkdf2DjangoCredManager {
|
||||||
|
cm := &Pbkdf2DjangoCredManager{}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||||
|
iterations := 260000
|
||||||
|
salt := userSalt
|
||||||
|
if salt == "" {
|
||||||
|
salt = organizationSalt
|
||||||
|
}
|
||||||
|
|
||||||
|
saltBytes := []byte(salt)
|
||||||
|
passwordBytes := []byte(password)
|
||||||
|
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iterations, sha256.Size, sha256.New)
|
||||||
|
hashBase64 := base64.StdEncoding.EncodeToString(computedHash)
|
||||||
|
return "pbkdf2_sha256$" + strconv.Itoa(iterations) + "$" + salt + "$" + hashBase64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Pbkdf2DjangoCredManager) IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool {
|
||||||
|
parts := strings.Split(passwordHash, "$")
|
||||||
|
if len(parts) != 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
algorithm, iterations, salt, hash := parts[0], parts[1], parts[2], parts[3]
|
||||||
|
if algorithm != "pbkdf2_sha256" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
iter, err := strconv.Atoi(iterations)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
saltBytes := []byte(salt)
|
||||||
|
passwordBytes := []byte(password)
|
||||||
|
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iter, sha256.Size, sha256.New)
|
||||||
|
computedHashBase64 := base64.StdEncoding.EncodeToString(computedHash)
|
||||||
|
|
||||||
|
return computedHashBase64 == hash
|
||||||
|
}
|
@ -70,6 +70,7 @@ type AuthForm struct {
|
|||||||
|
|
||||||
FaceId []float64 `json:"faceId"`
|
FaceId []float64 `json:"faceId"`
|
||||||
FaceIdImage []string `json:"faceIdImage"`
|
FaceIdImage []string `json:"faceIdImage"`
|
||||||
|
UserCode string `json:"userCode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {
|
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {
|
||||||
|
4
go.mod
4
go.mod
@ -16,7 +16,7 @@ require (
|
|||||||
github.com/casdoor/go-sms-sender v0.25.0
|
github.com/casdoor/go-sms-sender v0.25.0
|
||||||
github.com/casdoor/gomail/v2 v2.1.0
|
github.com/casdoor/gomail/v2 v2.1.0
|
||||||
github.com/casdoor/ldapserver v1.2.0
|
github.com/casdoor/ldapserver v1.2.0
|
||||||
github.com/casdoor/notify v1.0.0
|
github.com/casdoor/notify v1.0.1
|
||||||
github.com/casdoor/oss v1.8.0
|
github.com/casdoor/oss v1.8.0
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
||||||
github.com/casvisor/casvisor-go-sdk v1.4.0
|
github.com/casvisor/casvisor-go-sdk v1.4.0
|
||||||
@ -101,7 +101,7 @@ require (
|
|||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible // indirect
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible // indirect
|
||||||
github.com/aliyun/credentials-go v1.3.10 // indirect
|
github.com/aliyun/credentials-go v1.3.10 // indirect
|
||||||
github.com/apistd/uni-go-sdk v0.0.2 // indirect
|
github.com/apistd/uni-go-sdk v0.0.2 // indirect
|
||||||
github.com/atc0005/go-teams-notify/v2 v2.6.1 // indirect
|
github.com/atc0005/go-teams-notify/v2 v2.13.0 // indirect
|
||||||
github.com/baidubce/bce-sdk-go v0.9.156 // indirect
|
github.com/baidubce/bce-sdk-go v0.9.156 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/blinkbean/dingtalk v0.0.0-20210905093040-7d935c0f7e19 // indirect
|
github.com/blinkbean/dingtalk v0.0.0-20210905093040-7d935c0f7e19 // indirect
|
||||||
|
8
go.sum
8
go.sum
@ -188,8 +188,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
|
|||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/atc0005/go-teams-notify/v2 v2.6.1 h1:t22ybzQuaQs4UJe4ceF5VYGsPhs6ir3nZOId/FBy6Go=
|
github.com/atc0005/go-teams-notify/v2 v2.13.0 h1:nbDeHy89NjYlF/PEfLVF6lsserY9O5SnN1iOIw3AxXw=
|
||||||
github.com/atc0005/go-teams-notify/v2 v2.6.1/go.mod h1:xo6GejLDHn3tWBA181F8LrllIL0xC1uRsRxq7YNXaaY=
|
github.com/atc0005/go-teams-notify/v2 v2.13.0/go.mod h1:WSv9moolRsBcpZbwEf6gZxj7h0uJlJskJq5zkEWKO8Y=
|
||||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||||
github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||||
github.com/aws/aws-sdk-go v1.45.5 h1:bxilnhv9FngUgdPNJmOIv2bk+2sP0dpqX3e4olhWcGM=
|
github.com/aws/aws-sdk-go v1.45.5 h1:bxilnhv9FngUgdPNJmOIv2bk+2sP0dpqX3e4olhWcGM=
|
||||||
@ -238,8 +238,8 @@ github.com/casdoor/gomail/v2 v2.1.0 h1:ua97E3CARnF1Ik8ga/Drz9uGZfaElXJumFexiErWU
|
|||||||
github.com/casdoor/gomail/v2 v2.1.0/go.mod h1:GFzOD9RhY0nODiiPaQiOa6DfoKtmO9aTesu5qrp26OI=
|
github.com/casdoor/gomail/v2 v2.1.0/go.mod h1:GFzOD9RhY0nODiiPaQiOa6DfoKtmO9aTesu5qrp26OI=
|
||||||
github.com/casdoor/ldapserver v1.2.0 h1:HdSYe+ULU6z9K+2BqgTrJKQRR4//ERAXB64ttOun6Ow=
|
github.com/casdoor/ldapserver v1.2.0 h1:HdSYe+ULU6z9K+2BqgTrJKQRR4//ERAXB64ttOun6Ow=
|
||||||
github.com/casdoor/ldapserver v1.2.0/go.mod h1:VwYU2vqQ2pA8sa00PRekH71R2XmgfzMKhmp1XrrDu2s=
|
github.com/casdoor/ldapserver v1.2.0/go.mod h1:VwYU2vqQ2pA8sa00PRekH71R2XmgfzMKhmp1XrrDu2s=
|
||||||
github.com/casdoor/notify v1.0.0 h1:oldsaaQFPrlufm/OA314z8DwFVE1Tc9Gt1z4ptRHhXw=
|
github.com/casdoor/notify v1.0.1 h1:p0kzI7OBlvLbL7zWeKIu31LRcEAygNZGKr5gcFfSIoE=
|
||||||
github.com/casdoor/notify v1.0.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
github.com/casdoor/notify v1.0.1/go.mod h1:RUlaFJw87FoM/nbs0iXPP0h+DxKGTaWAIFQV0oZcSQA=
|
||||||
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
|
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
|
||||||
github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U=
|
github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U=
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Failed to add user": "Gagal menambahkan pengguna",
|
"Failed to add user": "Gagal menambahkan pengguna",
|
||||||
"Get init score failed, error: %w": "Gagal mendapatkan nilai init, kesalahan: %w",
|
"Get init score failed, error: %w": "Gagal mendapatkan nilai inisiasi, kesalahan: %w",
|
||||||
"Please sign out first": "Silakan keluar terlebih dahulu",
|
"Please sign out first": "Silakan keluar terlebih dahulu",
|
||||||
"The application does not allow to sign up new account": "Aplikasi tidak memperbolehkan untuk mendaftar akun baru"
|
"The application does not allow to sign up new account": "Aplikasi tidak memperbolehkan pendaftaran akun baru"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"Challenge method should be S256": "Metode tantangan harus S256",
|
"Challenge method should be S256": "Metode tantangan harus S256",
|
||||||
@ -13,17 +13,17 @@
|
|||||||
"State expected: %s, but got: %s": "Diharapkan: %s, tapi diperoleh: %s",
|
"State expected: %s, but got: %s": "Diharapkan: %s, tapi diperoleh: %s",
|
||||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "Akun untuk penyedia: %s dan nama pengguna: %s (%s) tidak ada dan tidak diizinkan untuk mendaftar sebagai akun baru melalui %%s, silakan gunakan cara lain untuk mendaftar",
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "Akun untuk penyedia: %s dan nama pengguna: %s (%s) tidak ada dan tidak diizinkan untuk mendaftar sebagai akun baru melalui %%s, silakan gunakan cara lain untuk mendaftar",
|
||||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Akun untuk penyedia: %s dan nama pengguna: %s (%s) tidak ada dan tidak diizinkan untuk mendaftar sebagai akun baru, silakan hubungi dukungan IT Anda",
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Akun untuk penyedia: %s dan nama pengguna: %s (%s) tidak ada dan tidak diizinkan untuk mendaftar sebagai akun baru, silakan hubungi dukungan IT Anda",
|
||||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Akun untuk provider: %s dan username: %s (%s) sudah terhubung dengan akun lain: %s (%s)",
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Akun untuk penyedia: %s dan username: %s (%s) sudah terhubung dengan akun lain: %s (%s)",
|
||||||
"The application: %s does not exist": "Aplikasi: %s tidak ada",
|
"The application: %s does not exist": "Aplikasi: %s tidak ada",
|
||||||
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
|
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
|
||||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||||
"The login method: login with face is not enabled for the application": "The login method: login with face is not enabled for the application",
|
"The login method: login with face is not enabled for the application": "The login method: login with face is not enabled for the application",
|
||||||
"The login method: login with password is not enabled for the application": "Metode login: login dengan kata sandi tidak diaktifkan untuk aplikasi tersebut",
|
"The login method: login with password is not enabled for the application": "Metode login: login dengan sandi tidak diaktifkan untuk aplikasi tersebut",
|
||||||
"The organization: %s does not exist": "The organization: %s does not exist",
|
"The organization: %s does not exist": "The organization: %s does not exist",
|
||||||
"The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
|
"The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
|
||||||
"Unauthorized operation": "Operasi tidak sah",
|
"Unauthorized operation": "Operasi tidak sah",
|
||||||
"Unknown authentication type (not password or provider), form = %s": "Jenis otentikasi tidak diketahui (bukan kata sandi atau pemberi), formulir = %s",
|
"Unknown authentication type (not password or provider), form = %s": "Jenis otentikasi tidak diketahui (bukan sandi atau penyedia), formulir = %s",
|
||||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||||
},
|
},
|
||||||
@ -39,59 +39,59 @@
|
|||||||
"Email cannot be empty": "Email tidak boleh kosong",
|
"Email cannot be empty": "Email tidak boleh kosong",
|
||||||
"Email is invalid": "Email tidak valid",
|
"Email is invalid": "Email tidak valid",
|
||||||
"Empty username.": "Nama pengguna kosong.",
|
"Empty username.": "Nama pengguna kosong.",
|
||||||
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
|
"Face data does not exist, cannot log in": "Data wajah tidak ada, tidak bisa login",
|
||||||
"Face data mismatch": "Face data mismatch",
|
"Face data mismatch": "Ketidakcocokan data wajah",
|
||||||
"FirstName cannot be blank": "Nama depan tidak boleh kosong",
|
"FirstName cannot be blank": "Nama depan tidak boleh kosong",
|
||||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
"Invitation code cannot be blank": "Kode undangan tidak boleh kosong",
|
||||||
"Invitation code exhausted": "Invitation code exhausted",
|
"Invitation code exhausted": "Kode undangan habis",
|
||||||
"Invitation code is invalid": "Invitation code is invalid",
|
"Invitation code is invalid": "Kode undangan tidak valid",
|
||||||
"Invitation code suspended": "Invitation code suspended",
|
"Invitation code suspended": "Kode undangan ditangguhkan",
|
||||||
"LDAP user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
|
"LDAP user name or password incorrect": "Nama pengguna atau sandi LDAP salah",
|
||||||
"LastName cannot be blank": "Nama belakang tidak boleh kosong",
|
"LastName cannot be blank": "Nama belakang tidak boleh kosong",
|
||||||
"Multiple accounts with same uid, please check your ldap server": "Beberapa akun dengan uid yang sama, harap periksa server ldap Anda",
|
"Multiple accounts with same uid, please check your ldap server": "Beberapa akun dengan uid yang sama, harap periksa server LDAP Anda",
|
||||||
"Organization does not exist": "Organisasi tidak ada",
|
"Organization does not exist": "Organisasi tidak ada",
|
||||||
"Phone already exists": "Telepon sudah ada",
|
"Phone already exists": "Telepon sudah ada",
|
||||||
"Phone cannot be empty": "Telepon tidak boleh kosong",
|
"Phone cannot be empty": "Telepon tidak boleh kosong",
|
||||||
"Phone number is invalid": "Nomor telepon tidak valid",
|
"Phone number is invalid": "Nomor telepon tidak valid",
|
||||||
"Please register using the email corresponding to the invitation code": "Please register using the email corresponding to the invitation code",
|
"Please register using the email corresponding to the invitation code": "Silakan mendaftar menggunakan email yang sesuai dengan kode undangan",
|
||||||
"Please register using the phone corresponding to the invitation code": "Please register using the phone corresponding to the invitation code",
|
"Please register using the phone corresponding to the invitation code": "Silakan mendaftar menggunakan email yang sesuai dengan kode undangan",
|
||||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
"Please register using the username corresponding to the invitation code": "Silakan mendaftar menggunakan username yang sesuai dengan kode undangan",
|
||||||
"Session outdated, please login again": "Sesi kedaluwarsa, silakan masuk lagi",
|
"Session outdated, please login again": "Sesi kadaluwarsa, silakan masuk lagi",
|
||||||
"The invitation code has already been used": "The invitation code has already been used",
|
"The invitation code has already been used": "Kode undangan sudah digunakan",
|
||||||
"The user is forbidden to sign in, please contact the administrator": "Pengguna dilarang masuk, silakan hubungi administrator",
|
"The user is forbidden to sign in, please contact the administrator": "Pengguna dilarang masuk, silakan hubungi administrator",
|
||||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
"The user: %s doesn't exist in LDAP server": "Pengguna: %s tidak ada di server LDAP",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Nama pengguna hanya bisa menggunakan karakter alfanumerik, garis bawah atau tanda hubung, tidak boleh memiliki dua tanda hubung atau garis bawah berurutan, dan tidak boleh diawali atau diakhiri dengan tanda hubung atau garis bawah.",
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Nama pengguna hanya bisa menggunakan karakter alfanumerik, garis bawah atau tanda hubung, tidak boleh memiliki dua tanda hubung atau garis bawah berurutan, dan tidak boleh diawali atau diakhiri dengan tanda hubung atau garis bawah.",
|
||||||
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex",
|
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "Nilai \\\"%s\\\" pada bidang akun \\\"%s\\\" tidak cocok dengan ketentuan",
|
||||||
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"",
|
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "Nilai \\\"%s\\\" pada bidang pendaftaran \\\"%s\\\" tidak cocok dengan ketentuan aplikasi \\\"%s\\\"",
|
||||||
"Username already exists": "Nama pengguna sudah ada",
|
"Username already exists": "Nama pengguna sudah ada",
|
||||||
"Username cannot be an email address": "Username tidak bisa menjadi alamat email",
|
"Username cannot be an email address": "Username tidak bisa menjadi alamat email",
|
||||||
"Username cannot contain white spaces": "Username tidak boleh mengandung spasi",
|
"Username cannot contain white spaces": "Username tidak boleh mengandung spasi",
|
||||||
"Username cannot start with a digit": "Username tidak dapat dimulai dengan angka",
|
"Username cannot start with a digit": "Username tidak dapat dimulai dengan angka",
|
||||||
"Username is too long (maximum is 255 characters).": "Nama pengguna terlalu panjang (maksimum 255 karakter).",
|
"Username is too long (maximum is 255 characters).": "Nama pengguna terlalu panjang (maksimum 255 karakter).",
|
||||||
"Username must have at least 2 characters": "Nama pengguna harus memiliki setidaknya 2 karakter",
|
"Username must have at least 2 characters": "Nama pengguna harus memiliki setidaknya 2 karakter",
|
||||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Anda telah memasukkan kata sandi atau kode yang salah terlalu banyak kali, mohon tunggu selama %d menit dan coba lagi",
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Anda telah memasukkan sandi atau kode yang salah terlalu sering, mohon tunggu selama %d menit lalu coba kembali",
|
||||||
"Your region is not allow to signup by phone": "Wilayah Anda tidak diizinkan untuk mendaftar melalui telepon",
|
"Your region is not allow to signup by phone": "Wilayah Anda tidak diizinkan untuk mendaftar melalui telepon",
|
||||||
"password or code is incorrect": "password or code is incorrect",
|
"password or code is incorrect": "kata sandi atau kode salah",
|
||||||
"password or code is incorrect, you have %d remaining chances": "Kata sandi atau kode salah, Anda memiliki %d kesempatan tersisa",
|
"password or code is incorrect, you have %d remaining chances": "Sandi atau kode salah, Anda memiliki %d kesempatan tersisa",
|
||||||
"unsupported password type: %s": "jenis sandi tidak didukung: %s"
|
"unsupported password type: %s": "jenis sandi tidak didukung: %s"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"Missing parameter": "Parameter hilang",
|
"Missing parameter": "Parameter hilang",
|
||||||
"Please login first": "Silahkan login terlebih dahulu",
|
"Please login first": "Silahkan login terlebih dahulu",
|
||||||
"The organization: %s should have one application at least": "The organization: %s should have one application at least",
|
"The organization: %s should have one application at least": "Organisasi: %s setidaknya harus memiliki satu aplikasi",
|
||||||
"The user: %s doesn't exist": "Pengguna: %s tidak ada",
|
"The user: %s doesn't exist": "Pengguna: %s tidak ada",
|
||||||
"don't support captchaProvider: ": "Jangan mendukung captchaProvider:",
|
"don't support captchaProvider: ": "Jangan mendukung captchaProvider:",
|
||||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode",
|
"this operation is not allowed in demo mode": "tindakan ini tidak diizinkan pada mode demo",
|
||||||
"this operation requires administrator to perform": "this operation requires administrator to perform"
|
"this operation requires administrator to perform": "tindakan ini membutuhkan peran administrator"
|
||||||
},
|
},
|
||||||
"ldap": {
|
"ldap": {
|
||||||
"Ldap server exist": "Server ldap ada"
|
"Ldap server exist": "Server ldap ada"
|
||||||
},
|
},
|
||||||
"link": {
|
"link": {
|
||||||
"Please link first": "Tolong tautkan terlebih dahulu",
|
"Please link first": "Silahkan tautkan terlebih dahulu",
|
||||||
"This application has no providers": "Aplikasi ini tidak memiliki penyedia",
|
"This application has no providers": "Aplikasi ini tidak memiliki penyedia",
|
||||||
"This application has no providers of type": " Aplikasi ini tidak memiliki penyedia tipe ",
|
"This application has no providers of type": " Aplikasi ini tidak memiliki penyedia tipe ",
|
||||||
"This provider can't be unlinked": "Pemberi layanan ini tidak dapat dipisahkan",
|
"This provider can't be unlinked": "Penyedia layanan ini tidak dapat dipisahkan",
|
||||||
"You are not the global admin, you can't unlink other users": "Anda bukan admin global, Anda tidak dapat memutuskan tautan pengguna lain",
|
"You are not the global admin, you can't unlink other users": "Anda bukan admin global, Anda tidak dapat memutuskan tautan pengguna lain",
|
||||||
"You can't unlink yourself, you are not a member of any application": "Anda tidak dapat memutuskan tautan diri sendiri, karena Anda bukan anggota dari aplikasi apa pun"
|
"You can't unlink yourself, you are not a member of any application": "Anda tidak dapat memutuskan tautan diri sendiri, karena Anda bukan anggota dari aplikasi apa pun"
|
||||||
},
|
},
|
||||||
@ -101,11 +101,11 @@
|
|||||||
"Unknown modify rule %s.": "Aturan modifikasi tidak diketahui %s."
|
"Unknown modify rule %s.": "Aturan modifikasi tidak diketahui %s."
|
||||||
},
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
"The permission: \\\"%s\\\" doesn't exist": "The permission: \\\"%s\\\" doesn't exist"
|
"The permission: \\\"%s\\\" doesn't exist": "Izin: \\\"%s\\\" tidak ada"
|
||||||
},
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"Invalid application id": "ID aplikasi tidak valid",
|
"Invalid application id": "ID aplikasi tidak valid",
|
||||||
"the provider: %s does not exist": "provider: %s tidak ada"
|
"the provider: %s does not exist": "penyedia: %s tidak ada"
|
||||||
},
|
},
|
||||||
"resource": {
|
"resource": {
|
||||||
"User is nil for tag: avatar": "Pengguna kosong untuk tag: avatar",
|
"User is nil for tag: avatar": "Pengguna kosong untuk tag: avatar",
|
||||||
@ -129,13 +129,13 @@
|
|||||||
"token": {
|
"token": {
|
||||||
"Grant_type: %s is not supported in this application": "Jenis grant (grant_type) %s tidak didukung dalam aplikasi ini",
|
"Grant_type: %s is not supported in this application": "Jenis grant (grant_type) %s tidak didukung dalam aplikasi ini",
|
||||||
"Invalid application or wrong clientSecret": "Aplikasi tidak valid atau clientSecret salah",
|
"Invalid application or wrong clientSecret": "Aplikasi tidak valid atau clientSecret salah",
|
||||||
"Invalid client_id": "Invalid client_id = ID klien tidak valid",
|
"Invalid client_id": "ID klien tidak valid",
|
||||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "URI pengalihan: %s tidak ada dalam daftar URI Pengalihan yang diizinkan",
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "URI pengalihan: %s tidak ada dalam daftar URI Pengalihan yang diizinkan",
|
||||||
"Token not found, invalid accessToken": "Token tidak ditemukan, accessToken tidak valid"
|
"Token not found, invalid accessToken": "Token tidak ditemukan, accessToken tidak valid"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"Display name cannot be empty": "Nama tampilan tidak boleh kosong",
|
"Display name cannot be empty": "Nama tampilan tidak boleh kosong",
|
||||||
"New password cannot contain blank space.": "Kata sandi baru tidak boleh mengandung spasi kosong."
|
"New password cannot contain blank space.": "Sandi baru tidak boleh mengandung spasi kosong."
|
||||||
},
|
},
|
||||||
"user_upload": {
|
"user_upload": {
|
||||||
"Failed to import users": "Gagal mengimpor pengguna"
|
"Failed to import users": "Gagal mengimpor pengguna"
|
||||||
@ -148,16 +148,16 @@
|
|||||||
"verification": {
|
"verification": {
|
||||||
"Invalid captcha provider.": "Penyedia captcha tidak valid.",
|
"Invalid captcha provider.": "Penyedia captcha tidak valid.",
|
||||||
"Phone number is invalid in your region %s": "Nomor telepon tidak valid di wilayah anda %s",
|
"Phone number is invalid in your region %s": "Nomor telepon tidak valid di wilayah anda %s",
|
||||||
"The verification code has not been sent yet!": "The verification code has not been sent yet!",
|
"The verification code has not been sent yet!": "Kode verifikasi belum terkirim!",
|
||||||
"The verification code has not been sent yet, or has already been used!": "The verification code has not been sent yet, or has already been used!",
|
"The verification code has not been sent yet, or has already been used!": "Kode verifikasi belum dikirim atau telah digunakan!",
|
||||||
"Turing test failed.": "Tes Turing gagal.",
|
"Turing test failed.": "Tes Turing gagal.",
|
||||||
"Unable to get the email modify rule.": "Tidak dapat memperoleh aturan modifikasi email.",
|
"Unable to get the email modify rule.": "Tidak dapat memperoleh aturan modifikasi email.",
|
||||||
"Unable to get the phone modify rule.": "Tidak dapat memodifikasi aturan telepon.",
|
"Unable to get the phone modify rule.": "Tidak dapat memodifikasi aturan telepon.",
|
||||||
"Unknown type": "Tipe tidak diketahui",
|
"Unknown type": "Tipe tidak diketahui",
|
||||||
"Wrong verification code!": "Kode verifikasi salah!",
|
"Wrong verification code!": "Kode verifikasi salah!",
|
||||||
"You should verify your code in %d min!": "Anda harus memverifikasi kode Anda dalam %d menit!",
|
"You should verify your code in %d min!": "Anda harus memverifikasi kode Anda dalam %d menit!",
|
||||||
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "please add a SMS provider to the \\\"Providers\\\" list for the application: %s",
|
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "silahkan tambahkan penyedia SMS ke daftar \\\"Penyedia\\\" untuk aplikasi: %s",
|
||||||
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "please add an Email provider to the \\\"Providers\\\" list for the application: %s",
|
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "silahkan tambahkan penyedia Email ke daftar \\\"Penyedia\\\" untuk aplikasi: %s",
|
||||||
"the user does not exist, please sign up first": "Pengguna tidak ada, silakan daftar terlebih dahulu"
|
"the user does not exist, please sign up first": "Pengguna tidak ada, silakan daftar terlebih dahulu"
|
||||||
},
|
},
|
||||||
"webauthn": {
|
"webauthn": {
|
||||||
|
13
idp/goth.go
13
idp/goth.go
@ -278,9 +278,16 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Session: &naver.Session{},
|
Session: &naver.Session{},
|
||||||
}
|
}
|
||||||
case "Nextcloud":
|
case "Nextcloud":
|
||||||
idp = GothIdProvider{
|
if hostUrl != "" {
|
||||||
Provider: nextcloud.New(clientId, clientSecret, redirectUrl),
|
idp = GothIdProvider{
|
||||||
Session: &nextcloud.Session{},
|
Provider: nextcloud.NewCustomisedDNS(clientId, clientSecret, redirectUrl, hostUrl),
|
||||||
|
Session: &nextcloud.Session{},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: nextcloud.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &nextcloud.Session{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "OneDrive":
|
case "OneDrive":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
|
@ -44,6 +44,7 @@ type ProviderInfo struct {
|
|||||||
AppId string
|
AppId string
|
||||||
HostUrl string
|
HostUrl string
|
||||||
RedirectUrl string
|
RedirectUrl string
|
||||||
|
DisableSsl bool
|
||||||
|
|
||||||
TokenURL string
|
TokenURL string
|
||||||
AuthURL string
|
AuthURL string
|
||||||
@ -79,9 +80,9 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error
|
|||||||
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "WeCom":
|
case "WeCom":
|
||||||
if idpInfo.SubType == "Internal" {
|
if idpInfo.SubType == "Internal" {
|
||||||
return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.DisableSsl), nil
|
||||||
} else if idpInfo.SubType == "Third-party" {
|
} else if idpInfo.SubType == "Third-party" {
|
||||||
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.DisableSsl), nil
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
|
return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
|
||||||
}
|
}
|
||||||
|
@ -29,13 +29,16 @@ import (
|
|||||||
type WeComInternalIdProvider struct {
|
type WeComInternalIdProvider struct {
|
||||||
Client *http.Client
|
Client *http.Client
|
||||||
Config *oauth2.Config
|
Config *oauth2.Config
|
||||||
|
|
||||||
|
UseIdAsName bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWeComInternalIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComInternalIdProvider {
|
func NewWeComInternalIdProvider(clientId string, clientSecret string, redirectUrl string, useIdAsName bool) *WeComInternalIdProvider {
|
||||||
idp := &WeComInternalIdProvider{}
|
idp := &WeComInternalIdProvider{}
|
||||||
|
|
||||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
idp.Config = config
|
idp.Config = config
|
||||||
|
idp.UseIdAsName = useIdAsName
|
||||||
|
|
||||||
return idp
|
return idp
|
||||||
}
|
}
|
||||||
@ -169,5 +172,9 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
|
|||||||
userInfo.Id = userInfo.Username
|
userInfo.Id = userInfo.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if idp.UseIdAsName {
|
||||||
|
userInfo.Username = userInfo.Id
|
||||||
|
}
|
||||||
|
|
||||||
return &userInfo, nil
|
return &userInfo, nil
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,16 @@ import (
|
|||||||
type WeComIdProvider struct {
|
type WeComIdProvider struct {
|
||||||
Client *http.Client
|
Client *http.Client
|
||||||
Config *oauth2.Config
|
Config *oauth2.Config
|
||||||
|
|
||||||
|
UseIdAsName bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWeComIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComIdProvider {
|
func NewWeComIdProvider(clientId string, clientSecret string, redirectUrl string, useIdAsName bool) *WeComIdProvider {
|
||||||
idp := &WeComIdProvider{}
|
idp := &WeComIdProvider{}
|
||||||
|
|
||||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
idp.Config = config
|
idp.Config = config
|
||||||
|
idp.UseIdAsName = useIdAsName
|
||||||
|
|
||||||
return idp
|
return idp
|
||||||
}
|
}
|
||||||
@ -183,6 +186,10 @@ func (idp *WeComIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
DisplayName: wecomUserInfo.UserInfo.Name,
|
DisplayName: wecomUserInfo.UserInfo.Name,
|
||||||
AvatarUrl: wecomUserInfo.UserInfo.Avatar,
|
AvatarUrl: wecomUserInfo.UserInfo.Avatar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if idp.UseIdAsName {
|
||||||
|
userInfo.Username = userInfo.Id
|
||||||
|
}
|
||||||
return &userInfo, nil
|
return &userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
main.go
19
main.go
@ -15,6 +15,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/beego/beego"
|
"github.com/beego/beego"
|
||||||
@ -77,10 +78,26 @@ func main() {
|
|||||||
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
|
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
|
||||||
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
||||||
|
|
||||||
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
var logAdapter string
|
||||||
|
logConfigMap := make(map[string]interface{})
|
||||||
|
err := json.Unmarshal([]byte(conf.GetConfigString("logConfig")), &logConfigMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
_, ok := logConfigMap["adapter"]
|
||||||
|
if !ok {
|
||||||
|
logAdapter = "file"
|
||||||
|
} else {
|
||||||
|
logAdapter = logConfigMap["adapter"].(string)
|
||||||
|
}
|
||||||
|
if logAdapter == "console" {
|
||||||
|
logs.Reset()
|
||||||
|
}
|
||||||
|
err = logs.SetLogger(logAdapter, conf.GetConfigString("logConfig"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
port := beego.AppConfig.DefaultInt("httpport", 8000)
|
port := beego.AppConfig.DefaultInt("httpport", 8000)
|
||||||
// logs.SetLevel(logs.LevelInformational)
|
// logs.SetLevel(logs.LevelInformational)
|
||||||
logs.SetLogFuncCall(false)
|
logs.SetLogFuncCall(false)
|
||||||
|
@ -85,7 +85,7 @@ type Application struct {
|
|||||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
SamlReplyUrl string `xorm:"varchar(500)" json:"samlReplyUrl"`
|
||||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||||
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(3000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(3000)" json:"signupItems"`
|
||||||
|
@ -63,7 +63,11 @@ func GetCertCount(owner, field, value string) (int64, error) {
|
|||||||
|
|
||||||
func GetCerts(owner string) ([]*Cert, error) {
|
func GetCerts(owner string) ([]*Cert, error) {
|
||||||
certs := []*Cert{}
|
certs := []*Cert{}
|
||||||
err := ormer.Engine.Where("owner = ? or owner = ? ", "admin", owner).Desc("created_time").Find(&certs, &Cert{})
|
db := ormer.Engine.NewSession()
|
||||||
|
if owner != "" {
|
||||||
|
db = db.Where("owner = ? or owner = ? ", "admin", owner)
|
||||||
|
}
|
||||||
|
err := db.Desc("created_time").Find(&certs, &Cert{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return certs, err
|
return certs, err
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -210,6 +211,12 @@ func DeleteGroup(group *Group) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkGroupName(name string) error {
|
func checkGroupName(name string) error {
|
||||||
|
if name == "" {
|
||||||
|
return errors.New("group name can't be empty")
|
||||||
|
}
|
||||||
|
if strings.Contains(name, "/") {
|
||||||
|
return errors.New("group name can't contain \"/\"")
|
||||||
|
}
|
||||||
exist, err := ormer.Engine.Exist(&Organization{Owner: "admin", Name: name})
|
exist, err := ormer.Engine.Exist(&Organization{Owner: "admin", Name: name})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -30,6 +30,7 @@ type OidcDiscovery struct {
|
|||||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||||
TokenEndpoint string `json:"token_endpoint"`
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
||||||
|
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
|
||||||
JwksUri string `json:"jwks_uri"`
|
JwksUri string `json:"jwks_uri"`
|
||||||
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
||||||
ResponseTypesSupported []string `json:"response_types_supported"`
|
ResponseTypesSupported []string `json:"response_types_supported"`
|
||||||
@ -119,6 +120,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", originFrontend),
|
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", originFrontend),
|
||||||
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
|
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
|
||||||
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
||||||
|
DeviceAuthorizationEndpoint: fmt.Sprintf("%s/api/device-auth", originBackend),
|
||||||
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
||||||
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
||||||
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
|
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
|
||||||
@ -138,7 +140,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
|
|
||||||
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||||
jwks := jose.JSONWebKeySet{}
|
jwks := jose.JSONWebKeySet{}
|
||||||
certs, err := GetCerts("admin")
|
certs, err := GetCerts("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jwks, err
|
return jwks, err
|
||||||
}
|
}
|
||||||
@ -213,3 +215,14 @@ func GetWebFinger(resource string, rels []string, host string) (WebFinger, error
|
|||||||
|
|
||||||
return wf, nil
|
return wf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDeviceAuthResponse(deviceCode string, userCode string, host string) DeviceAuthResponse {
|
||||||
|
originFrontend, _ := getOriginFromHost(host)
|
||||||
|
|
||||||
|
return DeviceAuthResponse{
|
||||||
|
DeviceCode: deviceCode,
|
||||||
|
UserCode: userCode,
|
||||||
|
VerificationUri: fmt.Sprintf("%s/login/oauth/device/%s", originFrontend, userCode),
|
||||||
|
ExpiresIn: 120,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -179,7 +179,7 @@ func NewAdapterFromDb(driverName string, dataSourceName string, dbName string, d
|
|||||||
|
|
||||||
func refineDataSourceNameForPostgres(dataSourceName string) string {
|
func refineDataSourceNameForPostgres(dataSourceName string) string {
|
||||||
reg := regexp.MustCompile(`dbname=[^ ]+\s*`)
|
reg := regexp.MustCompile(`dbname=[^ ]+\s*`)
|
||||||
return reg.ReplaceAllString(dataSourceName, "")
|
return reg.ReplaceAllString(dataSourceName, "dbname=postgres")
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDatabaseForPostgres(driverName string, dataSourceName string, dbName string) error {
|
func createDatabaseForPostgres(driverName string, dataSourceName string, dbName string) error {
|
||||||
@ -190,7 +190,7 @@ func createDatabaseForPostgres(driverName string, dataSourceName string, dbName
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s;", dbName))
|
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE \"%s\";", dbName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !strings.Contains(err.Error(), "already exists") {
|
if !strings.Contains(err.Error(), "already exists") {
|
||||||
return err
|
return err
|
||||||
|
@ -475,6 +475,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
|
|||||||
AuthURL: provider.CustomAuthUrl,
|
AuthURL: provider.CustomAuthUrl,
|
||||||
UserInfoURL: provider.CustomUserInfoUrl,
|
UserInfoURL: provider.CustomUserInfoUrl,
|
||||||
UserMapping: provider.UserMapping,
|
UserMapping: provider.UserMapping,
|
||||||
|
DisableSsl: provider.DisableSsl,
|
||||||
}
|
}
|
||||||
|
|
||||||
if provider.Type == "WeChat" {
|
if provider.Type == "WeChat" {
|
||||||
|
@ -263,6 +263,27 @@ func addWebhookRecord(webhook *Webhook, record *casvisorsdk.Record, statusCode i
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterRecordObject(object string, objectFields []string) string {
|
||||||
|
var rawObject map[string]interface{}
|
||||||
|
_ = json.Unmarshal([]byte(object), &rawObject)
|
||||||
|
|
||||||
|
if rawObject == nil {
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredObject := make(map[string]interface{})
|
||||||
|
|
||||||
|
for _, field := range objectFields {
|
||||||
|
fieldValue, ok := rawObject[field]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filteredObject[field] = fieldValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.StructToJson(filteredObject)
|
||||||
|
}
|
||||||
|
|
||||||
func SendWebhooks(record *casvisorsdk.Record) error {
|
func SendWebhooks(record *casvisorsdk.Record) error {
|
||||||
webhooks, err := getWebhooksByOrganization("")
|
webhooks, err := getWebhooksByOrganization("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -271,7 +292,14 @@ func SendWebhooks(record *casvisorsdk.Record) error {
|
|||||||
|
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
webhooks = getFilteredWebhooks(webhooks, record.Organization, record.Action)
|
webhooks = getFilteredWebhooks(webhooks, record.Organization, record.Action)
|
||||||
|
|
||||||
|
record2 := *record
|
||||||
for _, webhook := range webhooks {
|
for _, webhook := range webhooks {
|
||||||
|
|
||||||
|
if len(webhook.ObjectFields) != 0 && webhook.ObjectFields[0] != "All" {
|
||||||
|
record2.Object = filterRecordObject(record.Object, webhook.ObjectFields)
|
||||||
|
}
|
||||||
|
|
||||||
var user *User
|
var user *User
|
||||||
if webhook.IsUserExtended {
|
if webhook.IsUserExtended {
|
||||||
user, err = getUser(record.Organization, record.User)
|
user, err = getUser(record.Organization, record.User)
|
||||||
@ -287,12 +315,12 @@ func SendWebhooks(record *casvisorsdk.Record) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statusCode, respBody, err := sendWebhook(webhook, record, user)
|
statusCode, respBody, err := sendWebhook(webhook, &record2, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = addWebhookRecord(webhook, record, statusCode, respBody, err)
|
err = addWebhookRecord(webhook, &record2, statusCode, respBody, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,8 @@ type Claims struct {
|
|||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Scope string `json:"scope,omitempty"`
|
Scope string `json:"scope,omitempty"`
|
||||||
// the `azp` (Authorized Party) claim. Optional. See https://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
// the `azp` (Authorized Party) claim. Optional. See https://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
||||||
Azp string `json:"azp,omitempty"`
|
Azp string `json:"azp,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +47,17 @@ type UserShort struct {
|
|||||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserStandard struct {
|
||||||
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
|
Name string `xorm:"varchar(100) notnull pk" json:"preferred_username,omitempty"`
|
||||||
|
|
||||||
|
Id string `xorm:"varchar(100) index" json:"id"`
|
||||||
|
DisplayName string `xorm:"varchar(100)" json:"name,omitempty"`
|
||||||
|
Avatar string `xorm:"varchar(500)" json:"picture,omitempty"`
|
||||||
|
Email string `xorm:"varchar(100) index" json:"email,omitempty"`
|
||||||
|
Phone string `xorm:"varchar(100) index" json:"phone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type UserWithoutThirdIdp struct {
|
type UserWithoutThirdIdp struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@ -140,6 +152,7 @@ type ClaimsShort struct {
|
|||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
Scope string `json:"scope,omitempty"`
|
Scope string `json:"scope,omitempty"`
|
||||||
Azp string `json:"azp,omitempty"`
|
Azp string `json:"azp,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +172,7 @@ type ClaimsWithoutThirdIdp struct {
|
|||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Scope string `json:"scope,omitempty"`
|
Scope string `json:"scope,omitempty"`
|
||||||
Azp string `json:"azp,omitempty"`
|
Azp string `json:"azp,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +190,20 @@ func getShortUser(user *User) *UserShort {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getStandardUser(user *User) *UserStandard {
|
||||||
|
res := &UserStandard{
|
||||||
|
Owner: user.Owner,
|
||||||
|
Name: user.Name,
|
||||||
|
|
||||||
|
Id: user.Id,
|
||||||
|
DisplayName: user.DisplayName,
|
||||||
|
Avatar: user.Avatar,
|
||||||
|
Email: user.Email,
|
||||||
|
Phone: user.Phone,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
||||||
res := &UserWithoutThirdIdp{
|
res := &UserWithoutThirdIdp{
|
||||||
Owner: user.Owner,
|
Owner: user.Owner,
|
||||||
@ -274,6 +302,7 @@ func getShortClaims(claims Claims) ClaimsShort {
|
|||||||
Scope: claims.Scope,
|
Scope: claims.Scope,
|
||||||
RegisteredClaims: claims.RegisteredClaims,
|
RegisteredClaims: claims.RegisteredClaims,
|
||||||
Azp: claims.Azp,
|
Azp: claims.Azp,
|
||||||
|
Provider: claims.Provider,
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -287,6 +316,7 @@ func getClaimsWithoutThirdIdp(claims Claims) ClaimsWithoutThirdIdp {
|
|||||||
Scope: claims.Scope,
|
Scope: claims.Scope,
|
||||||
RegisteredClaims: claims.RegisteredClaims,
|
RegisteredClaims: claims.RegisteredClaims,
|
||||||
Azp: claims.Azp,
|
Azp: claims.Azp,
|
||||||
|
Provider: claims.Provider,
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -308,6 +338,7 @@ func getClaimsCustom(claims Claims, tokenField []string) jwt.MapClaims {
|
|||||||
res["tag"] = claims.Tag
|
res["tag"] = claims.Tag
|
||||||
res["scope"] = claims.Scope
|
res["scope"] = claims.Scope
|
||||||
res["azp"] = claims.Azp
|
res["azp"] = claims.Azp
|
||||||
|
res["provider"] = claims.Provider
|
||||||
|
|
||||||
for _, field := range tokenField {
|
for _, field := range tokenField {
|
||||||
userField := userValue.FieldByName(field)
|
userField := userValue.FieldByName(field)
|
||||||
@ -342,7 +373,7 @@ func refineUser(user *User) *User {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateJwtToken(application *Application, user *User, nonce string, scope string, host string) (string, string, string, error) {
|
func generateJwtToken(application *Application, user *User, provider string, nonce string, scope string, host string) (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)
|
||||||
@ -362,9 +393,10 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
TokenType: "access-token",
|
TokenType: "access-token",
|
||||||
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,
|
Scope: scope,
|
||||||
Azp: application.ClientId,
|
Azp: application.ClientId,
|
||||||
|
Provider: provider,
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
Issuer: originBackend,
|
Issuer: originBackend,
|
||||||
Subject: user.Id,
|
Subject: user.Id,
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/i18n"
|
"github.com/casdoor/casdoor/i18n"
|
||||||
@ -37,6 +38,8 @@ const (
|
|||||||
EndpointError = "endpoint_error"
|
EndpointError = "endpoint_error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var DeviceAuthMap = sync.Map{}
|
||||||
|
|
||||||
type Code struct {
|
type Code struct {
|
||||||
Message string `xorm:"varchar(100)" json:"message"`
|
Message string `xorm:"varchar(100)" json:"message"`
|
||||||
Code string `xorm:"varchar(100)" json:"code"`
|
Code string `xorm:"varchar(100)" json:"code"`
|
||||||
@ -71,6 +74,22 @@ type IntrospectionResponse struct {
|
|||||||
Jti string `json:"jti,omitempty"`
|
Jti string `json:"jti,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeviceAuthCache struct {
|
||||||
|
UserSignIn bool
|
||||||
|
UserName string
|
||||||
|
ApplicationId string
|
||||||
|
Scope string
|
||||||
|
RequestAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceAuthResponse struct {
|
||||||
|
DeviceCode string `json:"device_code"`
|
||||||
|
UserCode string `json:"user_code"`
|
||||||
|
VerificationUri string `json:"verification_uri"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
Interval int `json:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
||||||
token, err := GetTokenByAccessToken(accessToken)
|
token, err := GetTokenByAccessToken(accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -117,7 +136,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
|
|||||||
return "", application, nil
|
return "", application, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) (*Code, error) {
|
func GetOAuthCode(userId string, clientId string, provider string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) (*Code, error) {
|
||||||
user, err := GetUser(userId)
|
user, err := GetUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -152,7 +171,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
|
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, provider, nonce, scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -222,6 +241,8 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||||
case "token", "id_token": // Implicit Grant
|
case "token", "id_token": // Implicit Grant
|
||||||
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
|
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
|
||||||
|
case "urn:ietf:params:oauth:grant-type:device_code":
|
||||||
|
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
|
||||||
case "refresh_token":
|
case "refresh_token":
|
||||||
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -358,7 +379,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", "", scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
Error: EndpointError,
|
Error: EndpointError,
|
||||||
@ -537,7 +558,7 @@ func GetPasswordToken(application *Application, username string, password string
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: EndpointError,
|
Error: EndpointError,
|
||||||
@ -583,7 +604,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
|||||||
Type: "application",
|
Type: "application",
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", scope, host)
|
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", "", scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: EndpointError,
|
Error: EndpointError,
|
||||||
@ -647,7 +668,7 @@ func GetTokenByUser(application *Application, user *User, scope string, nonce st
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
|
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", nonce, scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -754,7 +775,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", host)
|
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: EndpointError,
|
Error: EndpointError,
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ClaimsStandard struct {
|
type ClaimsStandard struct {
|
||||||
*UserShort
|
*UserStandard
|
||||||
EmailVerified bool `json:"email_verified,omitempty"`
|
EmailVerified bool `json:"email_verified,omitempty"`
|
||||||
PhoneNumber string `json:"phone_number,omitempty"`
|
PhoneNumber string `json:"phone_number,omitempty"`
|
||||||
PhoneNumberVerified bool `json:"phone_number_verified,omitempty"`
|
PhoneNumberVerified bool `json:"phone_number_verified,omitempty"`
|
||||||
@ -33,6 +33,7 @@ type ClaimsStandard struct {
|
|||||||
Scope string `json:"scope,omitempty"`
|
Scope string `json:"scope,omitempty"`
|
||||||
Address OIDCAddress `json:"address,omitempty"`
|
Address OIDCAddress `json:"address,omitempty"`
|
||||||
Azp string `json:"azp,omitempty"`
|
Azp string `json:"azp,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
@ -47,13 +48,14 @@ func getStreetAddress(user *User) string {
|
|||||||
|
|
||||||
func getStandardClaims(claims Claims) ClaimsStandard {
|
func getStandardClaims(claims Claims) ClaimsStandard {
|
||||||
res := ClaimsStandard{
|
res := ClaimsStandard{
|
||||||
UserShort: getShortUser(claims.User),
|
UserStandard: getStandardUser(claims.User),
|
||||||
EmailVerified: claims.User.EmailVerified,
|
EmailVerified: claims.User.EmailVerified,
|
||||||
TokenType: claims.TokenType,
|
TokenType: claims.TokenType,
|
||||||
Nonce: claims.Nonce,
|
Nonce: claims.Nonce,
|
||||||
Scope: claims.Scope,
|
Scope: claims.Scope,
|
||||||
RegisteredClaims: claims.RegisteredClaims,
|
RegisteredClaims: claims.RegisteredClaims,
|
||||||
Azp: claims.Azp,
|
Azp: claims.Azp,
|
||||||
|
Provider: claims.Provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Phone = ""
|
res.Phone = ""
|
||||||
|
@ -837,7 +837,7 @@ func AddUser(user *User) (bool, error) {
|
|||||||
return false, fmt.Errorf("the user's owner and name should not be empty")
|
return false, fmt.Errorf("the user's owner and name should not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if CheckUsername(user.Name, "en") != "" {
|
if CheckUsernameWithEmail(user.Name, "en") != "" {
|
||||||
user.Name = util.GetRandomName()
|
user.Name = util.GetRandomName()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ type Webhook struct {
|
|||||||
Headers []*Header `xorm:"mediumtext" json:"headers"`
|
Headers []*Header `xorm:"mediumtext" json:"headers"`
|
||||||
Events []string `xorm:"varchar(1000)" json:"events"`
|
Events []string `xorm:"varchar(1000)" json:"events"`
|
||||||
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
||||||
|
ObjectFields []string `xorm:"varchar(1000)" json:"objectFields"`
|
||||||
IsUserExtended bool `json:"isUserExtended"`
|
IsUserExtended bool `json:"isUserExtended"`
|
||||||
SingleOrgOnly bool `json:"singleOrgOnly"`
|
SingleOrgOnly bool `json:"singleOrgOnly"`
|
||||||
IsEnabled bool `json:"isEnabled"`
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
@ -66,6 +66,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
||||||
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
||||||
beego.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
|
beego.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
|
||||||
|
beego.Router("/api/device-auth", &controllers.ApiController{}, "POST:DeviceAuth")
|
||||||
|
|
||||||
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
||||||
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
||||||
|
@ -89,7 +89,7 @@ func fastAutoSignin(ctx *context.Context) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
code, err := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, ctx.Request.Host, getAcceptLanguage(ctx))
|
code, err := object.GetOAuthCode(userId, clientId, "", responseType, redirectUri, scope, state, nonce, codeChallenge, ctx.Request.Host, getAcceptLanguage(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if code.Message != "" {
|
} else if code.Message != "" {
|
||||||
|
@ -454,6 +454,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} disabled={this.state.application.tokenFormat !== "JWT-Custom"} mode="tags" showSearch style={{width: "100%"}} value={this.state.application.tokenFields} onChange={(value => {this.updateApplicationField("tokenFields", value);})}>
|
<Select virtual={false} disabled={this.state.application.tokenFormat !== "JWT-Custom"} mode="tags" showSearch style={{width: "100%"}} value={this.state.application.tokenFields} onChange={(value => {this.updateApplicationField("tokenFields", value);})}>
|
||||||
|
<Option key={"provider"} value={"provider"}>{"Provider"}</Option>)
|
||||||
{
|
{
|
||||||
Setting.getUserCommonFields().map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
Setting.getUserCommonFields().map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||||
}
|
}
|
||||||
@ -726,6 +727,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{id: "token", name: "Token"},
|
{id: "token", name: "Token"},
|
||||||
{id: "id_token", name: "ID Token"},
|
{id: "id_token", name: "ID Token"},
|
||||||
{id: "refresh_token", name: "Refresh Token"},
|
{id: "refresh_token", name: "Refresh Token"},
|
||||||
|
{id: "urn:ietf:params:oauth:grant-type:device_code", name: "Device Code"},
|
||||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
|
@ -119,6 +119,7 @@ class EntryPage extends React.Component {
|
|||||||
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
|
<Route exact path="/login/oauth/device/:userCode" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"device"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
<Route exact path="/forget" render={(props) => <SelfForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/forget" render={(props) => <SelfForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
<Route exact path="/forget/:applicationName" render={(props) => <ForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/forget/:applicationName" render={(props) => <ForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
|
@ -276,7 +276,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField("passwordType", value);})}
|
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField("passwordType", value);})}
|
||||||
options={["plain", "salt", "sha512-salt", "md5-salt", "bcrypt", "pbkdf2-salt", "argon2id"].map(item => Setting.getOption(item, item))}
|
options={["plain", "salt", "sha512-salt", "md5-salt", "bcrypt", "pbkdf2-salt", "argon2id", "pbkdf2-django"].map(item => Setting.getOption(item, item))}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -692,23 +692,35 @@ 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("general:Method"), i18next.t("provider:Method - Tooltip"))} :
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
</Col>
|
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - 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.method} onChange={value => {
|
||||||
}}>
|
this.updateProviderField("method", value);
|
||||||
{
|
}}>
|
||||||
[
|
{
|
||||||
{id: "Normal", name: i18next.t("provider:Normal")},
|
[
|
||||||
{id: "Silent", name: i18next.t("provider:Silent")},
|
{id: "Normal", name: i18next.t("provider:Normal")},
|
||||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
{id: "Silent", name: i18next.t("provider:Silent")},
|
||||||
}
|
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||||
</Select>
|
}
|
||||||
</Col>
|
</Select>
|
||||||
</Row>)
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Use id as name"), i18next.t("provider:Use id as name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Switch checked={this.state.provider.disableSsl} onChange={checked => {
|
||||||
|
this.updateProviderField("disableSsl", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</React.Fragment>)
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
@ -938,7 +950,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && (this.state.provider.type !== "Casdoor" && this.state.category !== "Storage") && this.state.provider.type !== "Okta" ? null : (
|
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && (this.state.provider.type !== "Casdoor" && this.state.category !== "Storage") && this.state.provider.type !== "Okta" && this.state.provider.type !== "Nextcloud" ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
|
@ -1616,7 +1616,7 @@ export function isDarkTheme(themeAlgorithm) {
|
|||||||
|
|
||||||
function getPreferredMfaProp(mfaProps) {
|
function getPreferredMfaProp(mfaProps) {
|
||||||
for (const i in mfaProps) {
|
for (const i in mfaProps) {
|
||||||
if (mfaProps[i].isPreffered) {
|
if (mfaProps[i].isPreferred) {
|
||||||
return mfaProps[i];
|
return mfaProps[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,6 +144,9 @@ class WebhookEditPage extends React.Component {
|
|||||||
if (["port"].includes(key)) {
|
if (["port"].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
}
|
}
|
||||||
|
if (key === "objectFields") {
|
||||||
|
value = value.includes("All") ? ["All"] : value;
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,6 +297,19 @@ class WebhookEditPage extends React.Component {
|
|||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("webhook:Object fields"), i18next.t("webhook:Object fields - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="tags" showSearch style={{width: "100%"}} value={this.state.webhook.objectFields} onChange={(value => {this.updateWebhookField("objectFields", value);})}>
|
||||||
|
<Option key="All" value="All">{i18next.t("general:All")}</Option>
|
||||||
|
{
|
||||||
|
["owner", "name", "createdTime", "updatedTime", "deletedTime", "id", "displayName"].map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
{Setting.getLabel(i18next.t("webhook:Is user extended"), i18next.t("webhook:Is user extended - Tooltip"))} :
|
{Setting.getLabel(i18next.t("webhook:Is user extended"), i18next.t("webhook:Is user extended - Tooltip"))} :
|
||||||
|
@ -37,7 +37,7 @@ export function signup(values) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getEmailAndPhone(organization, username) {
|
export function getEmailAndPhone(organization, username) {
|
||||||
return fetch(`${authConfig.serverUrl}/api/get-email-and-phone?organization=${organization}&username=${username}`, {
|
return fetch(`${authConfig.serverUrl}/api/get-email-and-phone?organization=${organization}&username=${encodeURIComponent(username)}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@ -61,7 +61,14 @@ export function oAuthParamsToQuery(oAuthParams) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getApplicationLogin(params) {
|
export function getApplicationLogin(params) {
|
||||||
const queryParams = (params?.type === "cas") ? casLoginParamsToQuery(params) : oAuthParamsToQuery(params);
|
let queryParams = "";
|
||||||
|
if (params?.type === "cas") {
|
||||||
|
queryParams = casLoginParamsToQuery(params);
|
||||||
|
} else if (params?.type === "device") {
|
||||||
|
queryParams = `?userCode=${params.userCode}&type=device`;
|
||||||
|
} else {
|
||||||
|
queryParams = oAuthParamsToQuery(params);
|
||||||
|
}
|
||||||
return fetch(`${authConfig.serverUrl}/api/get-app-login${queryParams}`, {
|
return fetch(`${authConfig.serverUrl}/api/get-app-login${queryParams}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
@ -193,7 +193,11 @@ class AuthCallback extends React.Component {
|
|||||||
const token = res.data;
|
const token = res.data;
|
||||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
|
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
|
||||||
} else if (responseType === "link") {
|
} else if (responseType === "link") {
|
||||||
const from = innerParams.get("from");
|
let from = innerParams.get("from");
|
||||||
|
const oauth = innerParams.get("oauth");
|
||||||
|
if (oauth) {
|
||||||
|
from += `?oauth=${oauth}`;
|
||||||
|
}
|
||||||
Setting.goToLinkSoftOrJumpSelf(this, from);
|
Setting.goToLinkSoftOrJumpSelf(this, from);
|
||||||
} else if (responseType === "saml") {
|
} else if (responseType === "saml") {
|
||||||
if (res.data2.method === "POST") {
|
if (res.data2.method === "POST") {
|
||||||
|
@ -65,6 +65,8 @@ class LoginPage extends React.Component {
|
|||||||
orgChoiceMode: new URLSearchParams(props.location?.search).get("orgChoiceMode") ?? null,
|
orgChoiceMode: new URLSearchParams(props.location?.search).get("orgChoiceMode") ?? null,
|
||||||
userLang: null,
|
userLang: null,
|
||||||
loginLoading: false,
|
loginLoading: false,
|
||||||
|
userCode: props.userCode ?? (props.match?.params?.userCode ?? null),
|
||||||
|
userCodeStatus: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
||||||
@ -81,7 +83,7 @@ class LoginPage extends React.Component {
|
|||||||
if (this.getApplicationObj() === undefined) {
|
if (this.getApplicationObj() === undefined) {
|
||||||
if (this.state.type === "login" || this.state.type === "saml") {
|
if (this.state.type === "login" || this.state.type === "saml") {
|
||||||
this.getApplication();
|
this.getApplication();
|
||||||
} else if (this.state.type === "code" || this.state.type === "cas") {
|
} else if (this.state.type === "code" || this.state.type === "cas" || this.state.type === "device") {
|
||||||
this.getApplicationLogin();
|
this.getApplicationLogin();
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
|
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
|
||||||
@ -155,13 +157,25 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getApplicationLogin() {
|
getApplicationLogin() {
|
||||||
const loginParams = (this.state.type === "cas") ? Util.getCasLoginParameters("admin", this.state.applicationName) : Util.getOAuthGetParameters();
|
let loginParams;
|
||||||
|
if (this.state.type === "cas") {
|
||||||
|
loginParams = Util.getCasLoginParameters("admin", this.state.applicationName);
|
||||||
|
} else if (this.state.type === "device") {
|
||||||
|
loginParams = {userCode: this.state.userCode, type: this.state.type};
|
||||||
|
} else {
|
||||||
|
loginParams = Util.getOAuthGetParameters();
|
||||||
|
}
|
||||||
AuthBackend.getApplicationLogin(loginParams)
|
AuthBackend.getApplicationLogin(loginParams)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const application = res.data;
|
const application = res.data;
|
||||||
this.onUpdateApplication(application);
|
this.onUpdateApplication(application);
|
||||||
} else {
|
} else {
|
||||||
|
if (this.state.type === "device") {
|
||||||
|
this.setState({
|
||||||
|
userCodeStatus: "expired",
|
||||||
|
});
|
||||||
|
}
|
||||||
this.onUpdateApplication(null);
|
this.onUpdateApplication(null);
|
||||||
this.setState({
|
this.setState({
|
||||||
msg: res.msg,
|
msg: res.msg,
|
||||||
@ -266,6 +280,9 @@ class LoginPage extends React.Component {
|
|||||||
|
|
||||||
onUpdateApplication(application) {
|
onUpdateApplication(application) {
|
||||||
this.props.onUpdateApplication(application);
|
this.props.onUpdateApplication(application);
|
||||||
|
if (application === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (const idx in application.providers) {
|
for (const idx in application.providers) {
|
||||||
const provider = application.providers[idx];
|
const provider = application.providers[idx];
|
||||||
if (provider.provider?.category === "Face ID") {
|
if (provider.provider?.category === "Face ID") {
|
||||||
@ -296,6 +313,9 @@ class LoginPage extends React.Component {
|
|||||||
const oAuthParams = Util.getOAuthGetParameters();
|
const oAuthParams = Util.getOAuthGetParameters();
|
||||||
|
|
||||||
values["type"] = oAuthParams?.responseType ?? this.state.type;
|
values["type"] = oAuthParams?.responseType ?? this.state.type;
|
||||||
|
if (this.state.userCode) {
|
||||||
|
values["userCode"] = this.state.userCode;
|
||||||
|
}
|
||||||
|
|
||||||
if (oAuthParams?.samlRequest) {
|
if (oAuthParams?.samlRequest) {
|
||||||
values["samlRequest"] = oAuthParams.samlRequest;
|
values["samlRequest"] = oAuthParams.samlRequest;
|
||||||
@ -479,6 +499,11 @@ class LoginPage extends React.Component {
|
|||||||
this.props.onLoginSuccess();
|
this.props.onLoginSuccess();
|
||||||
} else if (responseType === "code") {
|
} else if (responseType === "code") {
|
||||||
this.postCodeLoginAction(res);
|
this.postCodeLoginAction(res);
|
||||||
|
} else if (responseType === "device") {
|
||||||
|
Setting.showMessage("success", "Successful login");
|
||||||
|
this.setState({
|
||||||
|
userCodeStatus: "success",
|
||||||
|
});
|
||||||
} else if (responseType === "token" || responseType === "id_token") {
|
} else if (responseType === "token" || responseType === "id_token") {
|
||||||
if (res.data2) {
|
if (res.data2) {
|
||||||
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
||||||
@ -826,6 +851,16 @@ class LoginPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.userCode && this.state.userCodeStatus === "success") {
|
||||||
|
return (
|
||||||
|
<Result
|
||||||
|
status="success"
|
||||||
|
title={i18next.t("application:Logged in successfully")}
|
||||||
|
>
|
||||||
|
</Result>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application) || Setting.isFaceIdEnabled(application);
|
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application) || Setting.isFaceIdEnabled(application);
|
||||||
if (showForm) {
|
if (showForm) {
|
||||||
let loginWidth = 320;
|
let loginWidth = 320;
|
||||||
@ -986,6 +1021,10 @@ class LoginPage extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.userCode && this.state.userCodeStatus === "success") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{fontSize: 16, textAlign: "left"}}>
|
<div style={{fontSize: 16, textAlign: "left"}}>
|
||||||
@ -1268,6 +1307,15 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (this.state.userCodeStatus === "expired") {
|
||||||
|
return <Result
|
||||||
|
style={{width: "100%"}}
|
||||||
|
status="error"
|
||||||
|
title={`Code ${i18next.t("subscription:Expired")}`}
|
||||||
|
>
|
||||||
|
</Result>;
|
||||||
|
}
|
||||||
|
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
if (application === undefined) {
|
if (application === undefined) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -194,8 +194,10 @@ class PromptPage extends React.Component {
|
|||||||
const redirectUri = params.get("redirectUri");
|
const redirectUri = params.get("redirectUri");
|
||||||
const code = params.get("code");
|
const code = params.get("code");
|
||||||
const state = params.get("state");
|
const state = params.get("state");
|
||||||
|
const oauth = params.get("oauth");
|
||||||
if (redirectUri === null || code === null || state === null) {
|
if (redirectUri === null || code === null || state === null) {
|
||||||
return "";
|
const signInUrl = sessionStorage.getItem("signinUrl");
|
||||||
|
return oauth === "true" ? signInUrl : "";
|
||||||
}
|
}
|
||||||
return `${redirectUri}?code=${code}&state=${state}`;
|
return `${redirectUri}?code=${code}&state=${state}`;
|
||||||
}
|
}
|
||||||
|
@ -402,6 +402,10 @@ export function getAuthUrl(application, provider, method, code) {
|
|||||||
redirectUri = `${redirectOrigin}/api/callback`;
|
redirectUri = `${redirectOrigin}/api/callback`;
|
||||||
} else if (provider.type === "Google" && provider.disableSsl) {
|
} else if (provider.type === "Google" && provider.disableSsl) {
|
||||||
scope += "+https://www.googleapis.com/auth/user.phonenumbers.read";
|
scope += "+https://www.googleapis.com/auth/user.phonenumbers.read";
|
||||||
|
} else if (provider.type === "Nextcloud") {
|
||||||
|
if (provider.domain) {
|
||||||
|
endpoint = `${provider.domain}/apps/oauth2/authorize`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "Facebook"
|
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "Facebook"
|
||||||
|
@ -195,8 +195,9 @@ class SignupPage extends React.Component {
|
|||||||
if (authConfig.appName === application.name) {
|
if (authConfig.appName === application.name) {
|
||||||
return "/result";
|
return "/result";
|
||||||
} else {
|
} else {
|
||||||
|
const oAuthParams = Util.getOAuthGetParameters();
|
||||||
if (Setting.hasPromptPage(application)) {
|
if (Setting.hasPromptPage(application)) {
|
||||||
return `/prompt/${application.name}`;
|
return `/prompt/${application.name}?oauth=${oAuthParams !== null}`;
|
||||||
} else {
|
} else {
|
||||||
return `/result/${application.name}`;
|
return `/result/${application.name}`;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user