mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-14 16:13:24 +08:00
Compare commits
83 Commits
Author | SHA1 | Date | |
---|---|---|---|
f35a5f9a47 | |||
7481b229a4 | |||
39e485ae82 | |||
764c64e67c | |||
e755a7331d | |||
6d9d595f86 | |||
d52058d2ae | |||
bcfbfc6947 | |||
75699c4a26 | |||
3e8bfb52a8 | |||
bbbd857a45 | |||
498900df76 | |||
7e3c1a6581 | |||
6e28043dba | |||
cb200687dc | |||
23bb0ee450 | |||
117259dfc5 | |||
e71d0476f0 | |||
b5d26767b2 | |||
5c4e22288e | |||
3ac4be64b8 | |||
97db54b6b9 | |||
3a19d4c7c8 | |||
a60be2b2ab | |||
06ef97a080 | |||
167c1b0f1b | |||
7d0eae230e | |||
901867e8bb | |||
b7be1943fa | |||
bbbda1982f | |||
e593f5be5b | |||
0918757e85 | |||
ce0d45a70b | |||
c4096788b2 | |||
523186f895 | |||
ef373ca736 | |||
721a681ff1 | |||
8b1c4b0c75 | |||
540f22f8bd | |||
79f81f1356 | |||
4e145f71b5 | |||
104f975a2f | |||
71bb400559 | |||
93c3c78d42 | |||
dd51bbbabf | |||
5318519bf8 | |||
d7c40459c0 | |||
de2932b5fb | |||
f4c873ffe6 | |||
97c7f2631a | |||
93f0425759 | |||
6a00657e42 | |||
88130bf020 | |||
5e99007fc9 | |||
66aca3124c | |||
61deb75c84 | |||
b8db07db4d | |||
a681c267b3 | |||
5fb6ea0ab4 | |||
0f6b7984d4 | |||
ba9d6e5d78 | |||
a4524e9996 | |||
b469928780 | |||
dc6fe13f75 | |||
8227762988 | |||
d92b072ed0 | |||
1161310f81 | |||
48ba5f91ed | |||
53df2c2704 | |||
78066da208 | |||
60096468fe | |||
39d6bc10f7 | |||
177f2f2f11 | |||
79b393afee | |||
5bb12a30d4 | |||
fdb68bf9c8 | |||
37748850c8 | |||
8968396ae5 | |||
f5395f15f9 | |||
73e44df867 | |||
0b575ccf84 | |||
9b7f465a47 | |||
b1fe28fb83 |
21
.github/workflows/build.yml
vendored
21
.github/workflows/build.yml
vendored
@ -127,7 +127,7 @@ jobs:
|
||||
release-and-push:
|
||||
name: Release And Push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
needs: [ frontend, backend, linter, e2e ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -182,14 +182,14 @@ jobs:
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Push to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
context: .
|
||||
target: STANDARD
|
||||
@ -199,7 +199,7 @@ jobs:
|
||||
|
||||
- name: Push All In One Version to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
context: .
|
||||
target: ALLINONE
|
||||
@ -217,13 +217,9 @@ jobs:
|
||||
- name: Update Helm Chart
|
||||
if: steps.should_push.outputs.push=='true'
|
||||
run: |
|
||||
# Set the appVersion of the chart to the current tag
|
||||
# Set the appVersion and version of the chart to the current tag
|
||||
sed -i "s/appVersion: .*/appVersion: ${{steps.get-current-tag.outputs.tag }}/g" ./charts/casdoor/Chart.yaml
|
||||
|
||||
# increase the patch version of the chart
|
||||
currentChartVersion=$(cat ./charts/casdoor/Chart.yaml | grep ^version | awk '{print $2}')
|
||||
newChartVersion=$(echo $currentChartVersion | awk -F. -v OFS=. '{$NF++;print}')
|
||||
sed -i "s/version: .*/version: $newChartVersion/g" ./charts/casdoor/Chart.yaml
|
||||
sed -i "s/version: .*/version: ${{steps.get-current-tag.outputs.tag }}/g" ./charts/casdoor/Chart.yaml
|
||||
|
||||
REGISTRY=oci://registry-1.docker.io/casbin
|
||||
cd charts/casdoor
|
||||
@ -235,7 +231,8 @@ jobs:
|
||||
|
||||
# Commit and push the changes back to the repository
|
||||
git config --global user.name "casbin-bot"
|
||||
git config --global user.email "casbin-bot@github.com"
|
||||
git config --global user.email "bot@casbin.org"
|
||||
git add Chart.yaml index.yaml
|
||||
git commit -m "chore(helm): bump helm charts appVersion to ${{steps.get-current-tag.outputs.tag }}"
|
||||
git push origin HEAD:master
|
||||
git tag ${{steps.get-current-tag.outputs.tag }}
|
||||
git push origin HEAD:master --follow-tags
|
||||
|
2
.github/workflows/sync.yml
vendored
2
.github/workflows/sync.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
|
@ -69,6 +69,7 @@ https://casdoor.org
|
||||
|
||||
- By source code: https://casdoor.org/docs/basic/server-installation
|
||||
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
||||
- By Kubernetes Helm: https://casdoor.org/docs/basic/try-with-helm
|
||||
|
||||
## How to connect to Casdoor?
|
||||
|
||||
@ -86,8 +87,7 @@ https://casdoor.org/docs/category/integrations
|
||||
## How to contact?
|
||||
|
||||
- Discord: https://discord.gg/5rPsrAzK7S
|
||||
- Forum: https://forum.casbin.com
|
||||
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e
|
||||
- Contact: https://casdoor.org/help
|
||||
|
||||
## Contribute
|
||||
|
||||
|
@ -51,7 +51,8 @@ p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, GET, /api/user, *, *
|
||||
p, *, *, GET, /api/health, *, *
|
||||
p, *, *, POST, /api/webhook, *, *
|
||||
p, *, *, *, /api/webhook, *, *
|
||||
p, *, *, GET, /api/get-qrcode, *, *
|
||||
p, *, *, GET, /api/get-webhook-event, *, *
|
||||
p, *, *, GET, /api/get-captcha-status, *, *
|
||||
p, *, *, *, /api/login/oauth, *, *
|
||||
@ -80,6 +81,7 @@ p, *, *, *, /.well-known/jwks, *, *
|
||||
p, *, *, GET, /api/get-saml-login, *, *
|
||||
p, *, *, POST, /api/acs, *, *
|
||||
p, *, *, GET, /api/saml/metadata, *, *
|
||||
p, *, *, *, /api/saml/redirect, *, *
|
||||
p, *, *, *, /cas, *, *
|
||||
p, *, *, *, /scim, *, *
|
||||
p, *, *, *, /api/webauthn, *, *
|
||||
@ -95,6 +97,7 @@ p, *, *, GET, /api/get-organization-names, *, *
|
||||
p, *, *, GET, /api/get-all-objects, *, *
|
||||
p, *, *, GET, /api/get-all-actions, *, *
|
||||
p, *, *, GET, /api/get-all-roles, *, *
|
||||
p, *, *, GET, /api/get-invitation-info, *, *
|
||||
`
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
@ -150,7 +153,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
||||
|
||||
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||
if method == "POST" {
|
||||
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") {
|
||||
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" || urlPath == "/api/verify-code" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") || urlPath == "/api/webhook" || urlPath == "/api/get-qrcode" {
|
||||
return true
|
||||
} else if urlPath == "/api/update-user" {
|
||||
// Allow ordinary users to update their own information
|
||||
|
14
conf/conf.go
14
conf/conf.go
@ -71,7 +71,10 @@ func GetConfigInt64(key string) (int64, error) {
|
||||
|
||||
func GetConfigDataSourceName() string {
|
||||
dataSourceName := GetConfigString("dataSourceName")
|
||||
return ReplaceDataSourceNameByDocker(dataSourceName)
|
||||
}
|
||||
|
||||
func ReplaceDataSourceNameByDocker(dataSourceName string) string {
|
||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||
if runningInDocker == "true" {
|
||||
// https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
|
||||
@ -81,7 +84,6 @@ func GetConfigDataSourceName() string {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
||||
}
|
||||
}
|
||||
|
||||
return dataSourceName
|
||||
}
|
||||
|
||||
@ -108,13 +110,3 @@ func GetConfigBatchSize() int {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func GetConfigRealDataSourceName(driverName string) string {
|
||||
var dataSourceName string
|
||||
if driverName != "mysql" {
|
||||
dataSourceName = GetConfigDataSourceName()
|
||||
} else {
|
||||
dataSourceName = GetConfigDataSourceName() + GetConfigString("dbName")
|
||||
}
|
||||
return dataSourceName
|
||||
}
|
||||
|
@ -93,6 +93,10 @@ func (c *ApiController) Signup() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
|
||||
if !application.EnableSignUp {
|
||||
c.ResponseError(c.T("account:The application does not allow to sign up new account"))
|
||||
@ -105,12 +109,27 @@ func (c *ApiController) Signup() {
|
||||
return
|
||||
}
|
||||
|
||||
if organization == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The organization: %s does not exist"), authForm.Organization))
|
||||
return
|
||||
}
|
||||
|
||||
msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
invitation, msg := object.CheckInvitationCode(application, organization, &authForm, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
invitationName := ""
|
||||
if invitation != nil {
|
||||
invitationName = invitation.Name
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && authForm.Email != "" {
|
||||
checkResult := object.CheckVerificationCode(authForm.Email, authForm.EmailCode, c.GetAcceptLanguage())
|
||||
if checkResult.Code != object.VerificationSuccess {
|
||||
@ -179,6 +198,8 @@ func (c *ApiController) Signup() {
|
||||
SignupApplication: application.Name,
|
||||
Properties: map[string]string{},
|
||||
Karma: 0,
|
||||
Invitation: invitationName,
|
||||
InvitationCode: authForm.InvitationCode,
|
||||
}
|
||||
|
||||
if len(organization.Tags) > 0 {
|
||||
@ -213,6 +234,15 @@ func (c *ApiController) Signup() {
|
||||
return
|
||||
}
|
||||
|
||||
if invitation != nil {
|
||||
invitation.UsedCount += 1
|
||||
_, err := object.UpdateInvitation(invitation.GetId(), invitation, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if application.HasPromptPage() && user.Type == "normal-user" {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(user.GetId())
|
||||
@ -453,7 +483,7 @@ func (c *ApiController) GetUserinfo2() {
|
||||
// GetCaptcha ...
|
||||
// @Tag Login API
|
||||
// @Title GetCaptcha
|
||||
// @router /api/get-captcha [get]
|
||||
// @router /get-captcha [get]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) GetCaptcha() {
|
||||
applicationId := c.Input().Get("applicationId")
|
||||
|
@ -110,14 +110,6 @@ func (c *ApiController) GetApplication() {
|
||||
}
|
||||
}
|
||||
|
||||
// 0 as an initialization value, corresponding to the default configuration parameters
|
||||
if application.FailedSigninLimit == 0 {
|
||||
application.FailedSigninLimit = object.DefaultFailedSigninLimit
|
||||
}
|
||||
if application.FailedSigninfrozenTime == 0 {
|
||||
application.FailedSigninfrozenTime = object.DefaultFailedSigninfrozenTime
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||
}
|
||||
|
||||
@ -147,6 +139,10 @@ func (c *ApiController) GetUserApplication() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The organization: %s should have one application at least"), user.Owner))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||
}
|
||||
|
@ -19,12 +19,11 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/casdoor/casdoor/captcha"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@ -37,11 +36,6 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
wechatScanType string
|
||||
lock sync.RWMutex
|
||||
)
|
||||
|
||||
func codeToResponse(code *object.Code) *Response {
|
||||
if code.Code == "" {
|
||||
return &Response{Status: "error", Msg: code.Message, Data: code.Code}
|
||||
@ -896,6 +890,7 @@ func (c *ApiController) GetSamlLogin() {
|
||||
authURL, method, err := object.GenerateSamlRequest(providerId, relayState, c.Ctx.Request.Host, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(authURL, method)
|
||||
}
|
||||
@ -906,62 +901,126 @@ func (c *ApiController) HandleSamlLogin() {
|
||||
decode, err := base64.StdEncoding.DecodeString(relayState)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
slice := strings.Split(string(decode), "&")
|
||||
relayState = url.QueryEscape(relayState)
|
||||
samlResponse = url.QueryEscape(samlResponse)
|
||||
targetUrl := fmt.Sprintf("%s?relayState=%s&samlResponse=%s",
|
||||
slice[4], relayState, samlResponse)
|
||||
c.Redirect(targetUrl, 303)
|
||||
c.Redirect(targetUrl, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// HandleOfficialAccountEvent ...
|
||||
// @Tag HandleOfficialAccountEvent API
|
||||
// @Tag System API
|
||||
// @Title HandleOfficialAccountEvent
|
||||
// @router /api/webhook [POST]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
// @router /webhook [POST]
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
func (c *ApiController) HandleOfficialAccountEvent() {
|
||||
respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body)
|
||||
if c.Ctx.Request.Method == "GET" {
|
||||
s := c.Ctx.Request.FormValue("echostr")
|
||||
echostr, _ := strconv.Atoi(s)
|
||||
c.SetData(echostr)
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
respBytes, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
signature := c.Input().Get("signature")
|
||||
timestamp := c.Input().Get("timestamp")
|
||||
nonce := c.Input().Get("nonce")
|
||||
var data struct {
|
||||
MsgType string `xml:"MsgType"`
|
||||
Event string `xml:"Event"`
|
||||
EventKey string `xml:"EventKey"`
|
||||
MsgType string `xml:"MsgType"`
|
||||
Event string `xml:"Event"`
|
||||
EventKey string `xml:"EventKey"`
|
||||
FromUserName string `xml:"FromUserName"`
|
||||
Ticket string `xml:"Ticket"`
|
||||
}
|
||||
err = xml.Unmarshal(respBytes, &data)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if data.EventKey != "" {
|
||||
wechatScanType = data.Event
|
||||
if strings.ToUpper(data.Event) != "SCAN" && strings.ToUpper(data.Event) != "SUBSCRIBE" {
|
||||
c.Ctx.WriteString("")
|
||||
return
|
||||
}
|
||||
if data.Ticket == "" {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
providerId := data.EventKey
|
||||
provider, err := object.GetProvider(providerId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if data.Ticket == "" {
|
||||
c.ResponseError("empty ticket")
|
||||
return
|
||||
}
|
||||
if !idp.VerifyWechatSignature(provider.Content, nonce, timestamp, signature) {
|
||||
c.ResponseError("invalid signature")
|
||||
return
|
||||
}
|
||||
|
||||
idp.Lock.Lock()
|
||||
if idp.WechatCacheMap == nil {
|
||||
idp.WechatCacheMap = make(map[string]idp.WechatCacheMapValue)
|
||||
}
|
||||
idp.WechatCacheMap[data.Ticket] = idp.WechatCacheMapValue{
|
||||
IsScanned: true,
|
||||
WechatUnionId: data.FromUserName,
|
||||
}
|
||||
idp.Lock.Unlock()
|
||||
|
||||
c.Ctx.WriteString("")
|
||||
}
|
||||
|
||||
// GetWebhookEventType ...
|
||||
// @Tag GetWebhookEventType API
|
||||
// @Tag System API
|
||||
// @Title GetWebhookEventType
|
||||
// @router /api/get-webhook-event [GET]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
// @router /get-webhook-event [GET]
|
||||
// @Param ticket query string true "The eventId of QRCode"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
func (c *ApiController) GetWebhookEventType() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
resp := &Response{
|
||||
Status: "ok",
|
||||
Msg: "",
|
||||
Data: wechatScanType,
|
||||
ticket := c.Input().Get("ticket")
|
||||
|
||||
idp.Lock.RLock()
|
||||
_, ok := idp.WechatCacheMap[ticket]
|
||||
idp.Lock.RUnlock()
|
||||
if !ok {
|
||||
c.ResponseError("ticket not found")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
wechatScanType = ""
|
||||
c.ServeJSON()
|
||||
|
||||
c.ResponseOk("SCAN", ticket)
|
||||
}
|
||||
|
||||
// GetQRCode
|
||||
// @Tag System API
|
||||
// @Title GetWechatQRCode
|
||||
// @router /get-qrcode [GET]
|
||||
// @Param id query string true "The id ( owner/name ) of provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
func (c *ApiController) GetQRCode() {
|
||||
providerId := c.Input().Get("id")
|
||||
provider, err := object.GetProvider(providerId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
code, ticket, err := idp.GetWechatOfficialAccountQRCode(provider.ClientId2, provider.ClientSecret2, providerId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(code, ticket)
|
||||
}
|
||||
|
||||
// GetCaptchaStatus
|
||||
@ -970,26 +1029,30 @@ func (c *ApiController) GetWebhookEventType() {
|
||||
// @Description Get Login Error Counts
|
||||
// @Param id query string true "The id ( owner/name ) of user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/get-captcha-status [get]
|
||||
// @router /get-captcha-status [get]
|
||||
func (c *ApiController) GetCaptchaStatus() {
|
||||
organization := c.Input().Get("organization")
|
||||
userId := c.Input().Get("user_id")
|
||||
userId := c.Input().Get("userId")
|
||||
user, err := object.GetUserByFields(organization, userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
failedSigninLimit, _, err := object.GetFailedSigninConfigByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
captchaEnabled := false
|
||||
if user != nil {
|
||||
var failedSigninLimit int
|
||||
failedSigninLimit, _, err = object.GetFailedSigninConfigByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user.SigninWrongTimes >= failedSigninLimit {
|
||||
captchaEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
var captchaEnabled bool
|
||||
if user != nil && user.SigninWrongTimes >= failedSigninLimit {
|
||||
captchaEnabled = true
|
||||
}
|
||||
c.ResponseOk(captchaEnabled)
|
||||
}
|
||||
|
||||
@ -997,7 +1060,7 @@ func (c *ApiController) GetCaptchaStatus() {
|
||||
// @Title Callback
|
||||
// @Tag Callback API
|
||||
// @Description Get Login Error Counts
|
||||
// @router /api/Callback [post]
|
||||
// @router /Callback [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) Callback() {
|
||||
code := c.GetString("code")
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
|
||||
// Enforce
|
||||
// @Title Enforce
|
||||
// @Tag Enforce API
|
||||
// @Tag Enforcer API
|
||||
// @Description Call Casbin Enforce API
|
||||
// @Param body body []string true "Casbin request"
|
||||
// @Param permissionId query string false "permission id"
|
||||
@ -121,6 +121,10 @@ func (c *ApiController) Enforce() {
|
||||
}
|
||||
} else if owner != "" {
|
||||
permissions, err = object.GetPermissions(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
@ -151,7 +155,7 @@ func (c *ApiController) Enforce() {
|
||||
|
||||
// BatchEnforce
|
||||
// @Title BatchEnforce
|
||||
// @Tag Enforce API
|
||||
// @Tag Enforcer API
|
||||
// @Description Call Casbin BatchEnforce API
|
||||
// @Param body body []string true "array of casbin requests"
|
||||
// @Param permissionId query string false "permission id"
|
||||
@ -235,6 +239,10 @@ func (c *ApiController) BatchEnforce() {
|
||||
}
|
||||
} else if owner != "" {
|
||||
permissions, err = object.GetPermissions(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
@ -264,10 +272,13 @@ func (c *ApiController) BatchEnforce() {
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllObjects() {
|
||||
userId := c.GetSessionUsername()
|
||||
userId := c.Input().Get("userId")
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
userId = c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
objects, err := object.GetAllObjects(userId)
|
||||
@ -280,10 +291,13 @@ func (c *ApiController) GetAllObjects() {
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllActions() {
|
||||
userId := c.GetSessionUsername()
|
||||
userId := c.Input().Get("userId")
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
userId = c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
actions, err := object.GetAllActions(userId)
|
||||
@ -296,10 +310,13 @@ func (c *ApiController) GetAllActions() {
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllRoles() {
|
||||
userId := c.GetSessionUsername()
|
||||
userId := c.Input().Get("userId")
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
userId = c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("general:Please login first"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
roles, err := object.GetAllRoles(userId)
|
||||
|
@ -39,13 +39,13 @@ func (c *ApiController) GetCerts() {
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
maskedCerts, err := object.GetMaskedCerts(object.GetCerts(owner))
|
||||
certs, err := object.GetMaskedCerts(object.GetCerts(owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedCerts)
|
||||
c.ResponseOk(certs)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetCertCount(owner, field, value)
|
||||
@ -80,13 +80,13 @@ func (c *ApiController) GetGlobalCerts() {
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
maskedCerts, err := object.GetMaskedCerts(object.GetGlobalCerts())
|
||||
certs, err := object.GetMaskedCerts(object.GetGlobalCerts())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedCerts)
|
||||
c.ResponseOk(certs)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetGlobalCertsCount(field, value)
|
||||
|
@ -18,7 +18,7 @@ import "github.com/casdoor/casdoor/object"
|
||||
|
||||
// GetDashboard
|
||||
// @Title GetDashboard
|
||||
// @Tag GetDashboard API
|
||||
// @Tag System API
|
||||
// @Description get information of dashboard
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-dashboard [get]
|
||||
|
@ -84,6 +84,32 @@ func (c *ApiController) GetInvitation() {
|
||||
c.ResponseOk(invitation)
|
||||
}
|
||||
|
||||
// GetInvitationCodeInfo
|
||||
// @Title GetInvitationCodeInfo
|
||||
// @Tag Invitation API
|
||||
// @Description get invitation code information
|
||||
// @Param code query string true "Invitation code"
|
||||
// @Success 200 {object} object.Invitation The Response object
|
||||
// @router /get-invitation-info [get]
|
||||
func (c *ApiController) GetInvitationCodeInfo() {
|
||||
code := c.Input().Get("code")
|
||||
applicationId := c.Input().Get("applicationId")
|
||||
|
||||
application, err := object.GetApplication(applicationId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
invitation, msg := object.GetInvitationByCode(code, application.Organization, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedInvitation(invitation))
|
||||
}
|
||||
|
||||
// UpdateInvitation
|
||||
// @Title UpdateInvitation
|
||||
// @Tag Invitation API
|
||||
@ -102,7 +128,7 @@ func (c *ApiController) UpdateInvitation() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateInvitation(id, &invitation))
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateInvitation(id, &invitation, c.GetAcceptLanguage()))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@ -121,7 +147,7 @@ func (c *ApiController) AddInvitation() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddInvitation(&invitation))
|
||||
c.Data["json"] = wrapActionResponse(object.AddInvitation(&invitation, c.GetAcceptLanguage()))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,14 @@ import (
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
MfaRecoveryCodesSession = "mfa_recovery_codes"
|
||||
MfaCountryCodeSession = "mfa_country_code"
|
||||
MfaDestSession = "mfa_dest"
|
||||
MfaTotpSecretSession = "mfa_totp_secret"
|
||||
)
|
||||
|
||||
// MfaSetupInitiate
|
||||
@ -57,12 +65,20 @@ func (c *ApiController) MfaSetupInitiate() {
|
||||
return
|
||||
}
|
||||
|
||||
mfaProps, err := MfaUtil.Initiate(c.Ctx, user.GetId())
|
||||
mfaProps, err := MfaUtil.Initiate(user.GetId())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
recoveryCode := uuid.NewString()
|
||||
c.SetSession(MfaRecoveryCodesSession, recoveryCode)
|
||||
if mfaType == object.TotpType {
|
||||
c.SetSession(MfaTotpSecretSession, mfaProps.Secret)
|
||||
}
|
||||
|
||||
mfaProps.RecoveryCodes = []string{recoveryCode}
|
||||
|
||||
resp := mfaProps
|
||||
c.ResponseOk(resp)
|
||||
}
|
||||
@ -83,13 +99,46 @@ func (c *ApiController) MfaSetupVerify() {
|
||||
c.ResponseError("missing auth type or passcode")
|
||||
return
|
||||
}
|
||||
mfaUtil := object.GetMfaUtil(mfaType, nil)
|
||||
|
||||
config := &object.MfaProps{
|
||||
MfaType: mfaType,
|
||||
}
|
||||
if mfaType == object.TotpType {
|
||||
secret := c.GetSession(MfaTotpSecretSession)
|
||||
if secret == nil {
|
||||
c.ResponseError("totp secret is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = secret.(string)
|
||||
} else if mfaType == object.SmsType {
|
||||
dest := c.GetSession(MfaDestSession)
|
||||
if dest == nil {
|
||||
c.ResponseError("destination is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = dest.(string)
|
||||
countryCode := c.GetSession(MfaCountryCodeSession)
|
||||
if countryCode == nil {
|
||||
c.ResponseError("country code is missing")
|
||||
return
|
||||
}
|
||||
config.CountryCode = countryCode.(string)
|
||||
} else if mfaType == object.EmailType {
|
||||
dest := c.GetSession(MfaDestSession)
|
||||
if dest == nil {
|
||||
c.ResponseError("destination is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = dest.(string)
|
||||
}
|
||||
|
||||
mfaUtil := object.GetMfaUtil(mfaType, config)
|
||||
if mfaUtil == nil {
|
||||
c.ResponseError("Invalid multi-factor authentication type")
|
||||
return
|
||||
}
|
||||
|
||||
err := mfaUtil.SetupVerify(c.Ctx, passcode)
|
||||
err := mfaUtil.SetupVerify(passcode)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
} else {
|
||||
@ -122,18 +171,69 @@ func (c *ApiController) MfaSetupEnable() {
|
||||
return
|
||||
}
|
||||
|
||||
mfaUtil := object.GetMfaUtil(mfaType, nil)
|
||||
config := &object.MfaProps{
|
||||
MfaType: mfaType,
|
||||
}
|
||||
|
||||
if mfaType == object.TotpType {
|
||||
secret := c.GetSession(MfaTotpSecretSession)
|
||||
if secret == nil {
|
||||
c.ResponseError("totp secret is missing")
|
||||
return
|
||||
}
|
||||
config.Secret = secret.(string)
|
||||
} else if mfaType == object.EmailType {
|
||||
if user.Email == "" {
|
||||
dest := c.GetSession(MfaDestSession)
|
||||
if dest == nil {
|
||||
c.ResponseError("destination is missing")
|
||||
return
|
||||
}
|
||||
user.Email = dest.(string)
|
||||
}
|
||||
} else if mfaType == object.SmsType {
|
||||
if user.Phone == "" {
|
||||
dest := c.GetSession(MfaDestSession)
|
||||
if dest == nil {
|
||||
c.ResponseError("destination is missing")
|
||||
return
|
||||
}
|
||||
user.Phone = dest.(string)
|
||||
countryCode := c.GetSession(MfaCountryCodeSession)
|
||||
if countryCode == nil {
|
||||
c.ResponseError("country code is missing")
|
||||
return
|
||||
}
|
||||
user.CountryCode = countryCode.(string)
|
||||
}
|
||||
}
|
||||
recoveryCodes := c.GetSession(MfaRecoveryCodesSession)
|
||||
if recoveryCodes == nil {
|
||||
c.ResponseError("recovery codes is missing")
|
||||
return
|
||||
}
|
||||
config.RecoveryCodes = []string{recoveryCodes.(string)}
|
||||
|
||||
mfaUtil := object.GetMfaUtil(mfaType, config)
|
||||
if mfaUtil == nil {
|
||||
c.ResponseError("Invalid multi-factor authentication type")
|
||||
return
|
||||
}
|
||||
|
||||
err = mfaUtil.Enable(c.Ctx, user)
|
||||
err = mfaUtil.Enable(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.DelSession(MfaRecoveryCodesSession)
|
||||
if mfaType == object.TotpType {
|
||||
c.DelSession(MfaTotpSecretSession)
|
||||
} else {
|
||||
c.DelSession(MfaCountryCodeSession)
|
||||
c.DelSession(MfaDestSession)
|
||||
}
|
||||
|
||||
c.ResponseOk(http.StatusText(http.StatusOK))
|
||||
}
|
||||
|
||||
|
@ -41,13 +41,12 @@ func (c *ApiController) GetOrganizations() {
|
||||
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
if limit == "" || page == "" {
|
||||
var maskedOrganizations []*object.Organization
|
||||
var organizations []*object.Organization
|
||||
var err error
|
||||
|
||||
if isGlobalAdmin {
|
||||
maskedOrganizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner))
|
||||
organizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner))
|
||||
} else {
|
||||
maskedOrganizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
|
||||
organizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -55,15 +54,15 @@ func (c *ApiController) GetOrganizations() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedOrganizations)
|
||||
c.ResponseOk(organizations)
|
||||
} else {
|
||||
if !isGlobalAdmin {
|
||||
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
|
||||
organizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(maskedOrganizations)
|
||||
c.ResponseOk(organizations)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetOrganizationCount(owner, field, value)
|
||||
@ -93,13 +92,13 @@ func (c *ApiController) GetOrganizations() {
|
||||
// @router /get-organization [get]
|
||||
func (c *ApiController) GetOrganization() {
|
||||
id := c.Input().Get("id")
|
||||
maskedOrganization, err := object.GetMaskedOrganization(object.GetOrganization(id))
|
||||
organization, err := object.GetMaskedOrganization(object.GetOrganization(id))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedOrganization)
|
||||
c.ResponseOk(organization)
|
||||
}
|
||||
|
||||
// UpdateOrganization ...
|
||||
@ -190,8 +189,8 @@ func (c *ApiController) GetDefaultApplication() {
|
||||
return
|
||||
}
|
||||
|
||||
maskedApplication := object.GetMaskedApplication(application, userId)
|
||||
c.ResponseOk(maskedApplication)
|
||||
application = object.GetMaskedApplication(application, userId)
|
||||
c.ResponseOk(application)
|
||||
}
|
||||
|
||||
// GetOrganizationNames ...
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
|
||||
// GetPrometheusInfo
|
||||
// @Title GetPrometheusInfo
|
||||
// @Tag Prometheus API
|
||||
// @Tag System API
|
||||
// @Description get Prometheus Info
|
||||
// @Success 200 {object} object.PrometheusInfo The Response object
|
||||
// @router /get-prometheus-info [get]
|
||||
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
@ -34,7 +35,13 @@ func (c *ApiController) GetSamlMeta() {
|
||||
return
|
||||
}
|
||||
|
||||
metadata, err := object.GetSamlMeta(application, host)
|
||||
enablePostBinding, err := c.GetBool("enablePostBinding", false)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
metadata, err := object.GetSamlMeta(application, host, enablePostBinding)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -43,3 +50,17 @@ func (c *ApiController) GetSamlMeta() {
|
||||
c.Data["xml"] = metadata
|
||||
c.ServeXML()
|
||||
}
|
||||
|
||||
func (c *ApiController) HandleSamlRedirect() {
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
owner := c.Ctx.Input.Param(":owner")
|
||||
application := c.Ctx.Input.Param(":application")
|
||||
|
||||
relayState := c.Input().Get("RelayState")
|
||||
samlRequest := c.Input().Get("SAMLRequest")
|
||||
|
||||
targetURL := object.GetSamlRedirectAddress(owner, application, relayState, samlRequest, host)
|
||||
|
||||
c.Redirect(targetURL, http.StatusSeeOther)
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ type NotificationForm struct {
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param from body controllers.EmailForm true "Details of the email request"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/send-email [post]
|
||||
// @router /send-email [post]
|
||||
func (c *ApiController) SendEmail() {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
@ -148,7 +148,7 @@ func (c *ApiController) SendEmail() {
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param from body controllers.SmsForm true "Details of the sms request"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/send-sms [post]
|
||||
// @router /send-sms [post]
|
||||
func (c *ApiController) SendSms() {
|
||||
provider, err := c.GetProviderFromContext("SMS")
|
||||
if err != nil {
|
||||
@ -186,7 +186,7 @@ func (c *ApiController) SendSms() {
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param from body controllers.NotificationForm true "Details of the notification request"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/send-notification [post]
|
||||
// @router /send-notification [post]
|
||||
func (c *ApiController) SendNotification() {
|
||||
provider, err := c.GetProviderFromContext("Notification")
|
||||
if err != nil {
|
||||
|
@ -40,13 +40,13 @@ func (c *ApiController) GetSyncers() {
|
||||
organization := c.Input().Get("organization")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
organizationSyncers, err := object.GetOrganizationSyncers(owner, organization)
|
||||
syncers, err := object.GetMaskedSyncers(object.GetOrganizationSyncers(owner, organization))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(organizationSyncers)
|
||||
c.ResponseOk(syncers)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetSyncerCount(owner, organization, field, value)
|
||||
@ -56,7 +56,7 @@ func (c *ApiController) GetSyncers() {
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
syncers, err := object.GetPaginationSyncers(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
syncers, err := object.GetMaskedSyncers(object.GetPaginationSyncers(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -76,7 +76,7 @@ func (c *ApiController) GetSyncers() {
|
||||
func (c *ApiController) GetSyncer() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
syncer, err := object.GetSyncer(id)
|
||||
syncer, err := object.GetMaskedSyncer(object.GetSyncer(id))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -47,6 +47,11 @@ func (c *ApiController) GetSystemInfo() {
|
||||
// @router /get-version-info [get]
|
||||
func (c *ApiController) GetVersionInfo() {
|
||||
versionInfo, err := util.GetVersionInfo()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if versionInfo.Version != "" {
|
||||
c.ResponseOk(versionInfo)
|
||||
return
|
||||
|
@ -156,7 +156,7 @@ func (c *ApiController) DeleteToken() {
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @Success 400 {object} object.TokenError The Response object
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router api/login/oauth/access_token [post]
|
||||
// @router /login/oauth/access_token [post]
|
||||
func (c *ApiController) GetOAuthToken() {
|
||||
clientId := c.Input().Get("client_id")
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
@ -271,8 +271,17 @@ func (c *ApiController) RefreshToken() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) ResponseTokenError(errorMsg string) {
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: errorMsg,
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// IntrospectToken
|
||||
// @Title IntrospectToken
|
||||
// @Tag Login API
|
||||
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a
|
||||
// parameter representing an OAuth 2.0 token and returns a JSON document
|
||||
// representing the meta information surrounding the
|
||||
@ -292,40 +301,33 @@ func (c *ApiController) IntrospectToken() {
|
||||
clientId = c.Input().Get("client_id")
|
||||
clientSecret = c.Input().Get("client_secret")
|
||||
if clientId == "" || clientSecret == "" {
|
||||
c.ResponseError(c.T("token:Empty clientId or clientSecret"))
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: object.InvalidRequest,
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
c.ResponseTokenError(object.InvalidRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
application, err := object.GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
c.ResponseTokenError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil || application.ClientSecret != clientSecret {
|
||||
c.ResponseError(c.T("token:Invalid application or wrong clientSecret"))
|
||||
c.Data["json"] = &object.TokenError{
|
||||
Error: object.InvalidClient,
|
||||
}
|
||||
c.SetTokenErrorHttpStatus()
|
||||
return
|
||||
}
|
||||
token, err := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
c.ResponseTokenError(c.T("token:Invalid application or wrong clientSecret"))
|
||||
return
|
||||
}
|
||||
|
||||
token, err := object.GetTokenByTokenValue(tokenValue)
|
||||
if err != nil {
|
||||
c.ResponseTokenError(err.Error())
|
||||
return
|
||||
}
|
||||
if token == nil {
|
||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
||||
if err != nil || jwtToken.Valid() != nil {
|
||||
// and token revoked case. but we not implement
|
||||
|
@ -39,13 +39,13 @@ func (c *ApiController) GetGlobalUsers() {
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
maskedUsers, err := object.GetMaskedUsers(object.GetGlobalUsers())
|
||||
users, err := object.GetMaskedUsers(object.GetGlobalUsers())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedUsers)
|
||||
c.ResponseOk(users)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetGlobalUserCount(field, value)
|
||||
@ -90,22 +90,22 @@ func (c *ApiController) GetUsers() {
|
||||
|
||||
if limit == "" || page == "" {
|
||||
if groupName != "" {
|
||||
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(util.GetId(owner, groupName)))
|
||||
users, err := object.GetMaskedUsers(object.GetGroupUsers(util.GetId(owner, groupName)))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(maskedUsers)
|
||||
c.ResponseOk(users)
|
||||
return
|
||||
}
|
||||
|
||||
maskedUsers, err := object.GetMaskedUsers(object.GetUsers(owner))
|
||||
users, err := object.GetMaskedUsers(object.GetUsers(owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedUsers)
|
||||
c.ResponseOk(users)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetUserCount(owner, field, value, groupName)
|
||||
@ -156,6 +156,10 @@ func (c *ApiController) GetUser() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if userFromUserId == nil {
|
||||
c.ResponseOk(nil)
|
||||
return
|
||||
}
|
||||
|
||||
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
|
||||
}
|
||||
@ -175,26 +179,6 @@ func (c *ApiController) GetUser() {
|
||||
owner = util.GetOwnerFromId(id)
|
||||
}
|
||||
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganization(util.GetId("admin", owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if organization == nil {
|
||||
c.ResponseError(fmt.Sprintf("the organization: %s is not found", owner))
|
||||
return
|
||||
}
|
||||
|
||||
if !organization.IsProfilePublic {
|
||||
requestUserId := c.GetSessionUsername()
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case email != "":
|
||||
user, err = object.GetUserByEmail(owner, email)
|
||||
@ -212,6 +196,29 @@ func (c *ApiController) GetUser() {
|
||||
return
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if organization == nil {
|
||||
c.ResponseError(fmt.Sprintf("the organization: %s is not found", owner))
|
||||
return
|
||||
}
|
||||
|
||||
if !organization.IsProfilePublic {
|
||||
requestUserId := c.GetSessionUsername()
|
||||
var hasPermission bool
|
||||
hasPermission, err = object.CheckUserPermission(requestUserId, user.GetId(), false, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
user.MultiFactorAuths = object.GetAllMfaProps(user, true)
|
||||
}
|
||||
@ -223,13 +230,13 @@ func (c *ApiController) GetUser() {
|
||||
}
|
||||
|
||||
isAdminOrSelf := c.IsAdminOrSelf(user)
|
||||
maskedUser, err := object.GetMaskedUser(user, isAdminOrSelf)
|
||||
user, err = object.GetMaskedUser(user, isAdminOrSelf)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedUser)
|
||||
c.ResponseOk(user)
|
||||
}
|
||||
|
||||
// UpdateUser
|
||||
@ -541,13 +548,13 @@ func (c *ApiController) GetSortedUsers() {
|
||||
sorter := c.Input().Get("sorter")
|
||||
limit := util.ParseInt(c.Input().Get("limit"))
|
||||
|
||||
maskedUsers, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
|
||||
users, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(maskedUsers)
|
||||
c.ResponseOk(users)
|
||||
}
|
||||
|
||||
// GetUserCount
|
||||
|
@ -109,6 +109,15 @@ func (c *ApiController) SendVerificationCode() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if user == nil || user.IsDeleted {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
|
||||
if user.IsForbidden {
|
||||
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// mfaUserSession != "", means method is MfaAuthVerification
|
||||
@ -152,7 +161,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
vform.Dest = mfaProps.Secret
|
||||
}
|
||||
} else if vform.Method == MfaSetupVerification {
|
||||
c.SetSession(object.MfaDestSession, vform.Dest)
|
||||
c.SetSession(MfaDestSession, vform.Dest)
|
||||
}
|
||||
|
||||
provider, err := application.GetEmailProvider()
|
||||
@ -189,8 +198,8 @@ func (c *ApiController) SendVerificationCode() {
|
||||
}
|
||||
|
||||
if vform.Method == MfaSetupVerification {
|
||||
c.SetSession(object.MfaCountryCodeSession, vform.CountryCode)
|
||||
c.SetSession(object.MfaDestSession, vform.Dest)
|
||||
c.SetSession(MfaCountryCodeSession, vform.CountryCode)
|
||||
c.SetSession(MfaDestSession, vform.Dest)
|
||||
}
|
||||
} else if vform.Method == MfaAuthVerification {
|
||||
mfaProps := user.GetPreferredMfaProps(false)
|
||||
@ -272,7 +281,7 @@ func (c *ApiController) VerifyCaptcha() {
|
||||
// ResetEmailOrPhone ...
|
||||
// @Tag Account API
|
||||
// @Title ResetEmailOrPhone
|
||||
// @router /api/reset-email-or-phone [post]
|
||||
// @router /reset-email-or-phone [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) ResetEmailOrPhone() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
@ -367,7 +376,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
// VerifyCode
|
||||
// @Tag Verification API
|
||||
// @Title VerifyCode
|
||||
// @router /api/verify-code [post]
|
||||
// @router /verify-code [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) VerifyCode() {
|
||||
var authForm form.AuthForm
|
||||
|
@ -24,6 +24,8 @@ func GetCredManager(passwordType string) CredManager {
|
||||
return NewPlainCredManager()
|
||||
} else if passwordType == "salt" {
|
||||
return NewSha256SaltCredManager()
|
||||
} else if passwordType == "sha512-salt" {
|
||||
return NewSha512SaltCredManager()
|
||||
} else if passwordType == "md5-salt" {
|
||||
return NewMd5UserSaltCredManager()
|
||||
} else if passwordType == "bcrypt" {
|
||||
|
50
cred/sha512-salt.go
Normal file
50
cred/sha512-salt.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright 2024 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/sha512"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
type Sha512SaltCredManager struct{}
|
||||
|
||||
func getSha512(data []byte) []byte {
|
||||
hash := sha512.Sum512(data)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func getSha512HexDigest(s string) string {
|
||||
b := getSha512([]byte(s))
|
||||
res := hex.EncodeToString(b)
|
||||
return res
|
||||
}
|
||||
|
||||
func NewSha512SaltCredManager() *Sha512SaltCredManager {
|
||||
cm := &Sha512SaltCredManager{}
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *Sha512SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
res := getSha512HexDigest(password)
|
||||
if organizationSalt != "" {
|
||||
res = getSha512HexDigest(res + organizationSalt)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (cm *Sha512SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
||||
}
|
12
form/auth.go
12
form/auth.go
@ -14,6 +14,8 @@
|
||||
|
||||
package form
|
||||
|
||||
import "reflect"
|
||||
|
||||
type AuthForm struct {
|
||||
Type string `json:"type"`
|
||||
SigninMethod string `json:"signinMethod"`
|
||||
@ -60,3 +62,13 @@ type AuthForm struct {
|
||||
Plan string `json:"plan"`
|
||||
Pricing string `json:"pricing"`
|
||||
}
|
||||
|
||||
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {
|
||||
val := reflect.ValueOf(*form)
|
||||
fieldValue := val.FieldByName(fieldName)
|
||||
|
||||
if fieldValue.IsValid() && fieldValue.Kind() == reflect.String {
|
||||
return true, fieldValue.String()
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
4
go.mod
4
go.mod
@ -12,7 +12,7 @@ require (
|
||||
github.com/casdoor/go-sms-sender v0.19.0
|
||||
github.com/casdoor/gomail/v2 v2.0.1
|
||||
github.com/casdoor/notify v0.45.0
|
||||
github.com/casdoor/oss v1.4.1
|
||||
github.com/casdoor/oss v1.5.0
|
||||
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
||||
github.com/casvisor/casvisor-go-sdk v1.0.3
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
@ -35,7 +35,7 @@ require (
|
||||
github.com/lestrrat-go/jwx v1.2.21
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||
github.com/markbates/goth v1.75.2
|
||||
github.com/markbates/goth v1.78.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nyaruka/phonenumbers v1.1.5
|
||||
github.com/pquerna/otp v1.4.0
|
||||
|
8
go.sum
8
go.sum
@ -1089,8 +1089,8 @@ github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR
|
||||
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
||||
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
||||
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
||||
github.com/casdoor/oss v1.4.1 h1:/P2JCyGzB2TtpJ3LocKocI1VAme2YdvVau2wpMQGt7I=
|
||||
github.com/casdoor/oss v1.4.1/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE=
|
||||
github.com/casdoor/oss v1.5.0 h1:mi1htaXR5fynskDry1S3wk+Dd2nRY1z1pVcnGsqMqP4=
|
||||
github.com/casdoor/oss v1.5.0/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE=
|
||||
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
||||
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
|
||||
github.com/casvisor/casvisor-go-sdk v1.0.3 h1:TKJQWKnhtznEBhzLPEdNsp7nJK2GgdD8JsB0lFPMW7U=
|
||||
@ -1657,8 +1657,8 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
||||
github.com/mailgun/mailgun-go/v4 v4.11.0/go.mod h1:L9s941Lgk7iB3TgywTPz074pK2Ekkg4kgbnAaAyJ2z8=
|
||||
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
|
||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
||||
github.com/markbates/goth v1.75.2 h1:C7KloBMMk50JyXaHhzfqWYLW6+bDcSVIvUGHXneLWro=
|
||||
github.com/markbates/goth v1.75.2/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc=
|
||||
github.com/markbates/goth v1.78.0 h1:7VEIFDycJp9deyVv3YraGBPdD0ZYQW93Y3Aw1eVP3BY=
|
||||
github.com/markbates/goth v1.78.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc=
|
||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Zugehörigkeit darf nicht leer sein",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "Anzeigename kann nicht leer sein",
|
||||
"DisplayName is not valid real name": "DisplayName ist kein gültiger Vorname",
|
||||
"Email already exists": "E-Mail existiert bereits",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Leerer Benutzername.",
|
||||
"FirstName cannot be blank": "Vorname darf nicht leer sein",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "Ldap Benutzername oder Passwort falsch",
|
||||
"LastName cannot be blank": "Nachname darf nicht leer sein",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Mehrere Konten mit derselben uid, bitte überprüfen Sie Ihren LDAP-Server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Telefon existiert bereits",
|
||||
"Phone cannot be empty": "Das Telefon darf nicht leer sein",
|
||||
"Phone number is invalid": "Die Telefonnummer ist ungültig",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Sitzung abgelaufen, bitte erneut anmelden",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Dem Benutzer ist der Zugang verboten, bitte kontaktieren Sie den Administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "Der Benutzername darf nur alphanumerische Zeichen, Unterstriche oder Bindestriche enthalten, keine aufeinanderfolgenden Bindestriche oder Unterstriche haben und darf nicht mit einem Bindestrich oder Unterstrich beginnen oder enden.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Benutzername existiert bereits",
|
||||
"Username cannot be an email address": "Benutzername kann keine E-Mail-Adresse sein",
|
||||
"Username cannot contain white spaces": "Benutzername darf keine Leerzeichen enthalten",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Afiliación no puede estar en blanco",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "El nombre de visualización no puede estar en blanco",
|
||||
"DisplayName is not valid real name": "El nombre de pantalla no es un nombre real válido",
|
||||
"Email already exists": "El correo electrónico ya existe",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Nombre de usuario vacío.",
|
||||
"FirstName cannot be blank": "El nombre no puede estar en blanco",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "Nombre de usuario o contraseña de Ldap incorrectos",
|
||||
"LastName cannot be blank": "El apellido no puede estar en blanco",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Cuentas múltiples con el mismo uid, por favor revise su servidor ldap",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "El teléfono ya existe",
|
||||
"Phone cannot be empty": "Teléfono no puede estar vacío",
|
||||
"Phone number is invalid": "El número de teléfono no es válido",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Sesión expirada, por favor vuelva a iniciar sesión",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "El usuario no está autorizado a iniciar sesión, por favor contacte al administrador",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "El nombre de usuario solo puede contener caracteres alfanuméricos, guiones bajos o guiones, no puede tener guiones o subrayados consecutivos, y no puede comenzar ni terminar con un guión o subrayado.",
|
||||
"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\\\"",
|
||||
"Username already exists": "El nombre de usuario ya existe",
|
||||
"Username cannot be an email address": "Nombre de usuario no puede ser una dirección de correo electrónico",
|
||||
"Username cannot contain white spaces": "Nombre de usuario no puede contener espacios en blanco",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation ne peut pas être vide",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "Le nom d'affichage ne peut pas être vide",
|
||||
"DisplayName is not valid real name": "DisplayName n'est pas un nom réel valide",
|
||||
"Email already exists": "E-mail déjà existant",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Nom d'utilisateur vide.",
|
||||
"FirstName cannot be blank": "Le prénom ne peut pas être laissé vide",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "Nom d'utilisateur ou mot de passe LDAP incorrect",
|
||||
"LastName cannot be blank": "Le nom de famille ne peut pas être vide",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Plusieurs comptes avec le même identifiant d'utilisateur, veuillez vérifier votre serveur LDAP",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Le téléphone existe déjà",
|
||||
"Phone cannot be empty": "Le téléphone ne peut pas être vide",
|
||||
"Phone number is invalid": "Le numéro de téléphone est invalide",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
|
||||
"The user: %s doesn't exist in LDAP server": "L'utilisateur %s n'existe pas sur le serveur 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.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Nom d'utilisateur existe déjà",
|
||||
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
|
||||
"Username cannot contain white spaces": "Nom d'utilisateur ne peut pas contenir d'espaces blancs",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Keterkaitan tidak boleh kosong",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "Nama Pengguna tidak boleh kosong",
|
||||
"DisplayName is not valid real name": "DisplayName bukanlah nama asli yang valid",
|
||||
"Email already exists": "Email sudah ada",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Nama pengguna kosong.",
|
||||
"FirstName cannot be blank": "Nama depan tidak boleh kosong",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
|
||||
"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",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Telepon sudah ada",
|
||||
"Phone cannot be empty": "Telepon tidak boleh kosong",
|
||||
"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 phone corresponding to the invitation code": "Please register using the phone 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",
|
||||
"Session outdated, please login again": "Sesi kedaluwarsa, silakan masuk lagi",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"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 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 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\\\"",
|
||||
"Username already exists": "Nama pengguna sudah ada",
|
||||
"Username cannot be an email address": "Username tidak bisa menjadi alamat email",
|
||||
"Username cannot contain white spaces": "Username tidak boleh mengandung spasi",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "所属は空白にできません",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "表示名は空白にできません",
|
||||
"DisplayName is not valid real name": "表示名は有効な実名ではありません",
|
||||
"Email already exists": "メールは既に存在します",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "空のユーザー名。",
|
||||
"FirstName cannot be blank": "ファーストネームは空白にできません",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
|
||||
"LastName cannot be blank": "姓は空白にできません",
|
||||
"Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "電話はすでに存在しています",
|
||||
"Phone cannot be empty": "電話は空っぽにできません",
|
||||
"Phone number is invalid": "電話番号が無効です",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "セッションが期限切れになりました。再度ログインしてください",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "ユーザーはサインインできません。管理者に連絡してください",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "ユーザー名には英数字、アンダースコア、ハイフンしか含めることができません。連続したハイフンまたはアンダースコアは不可であり、ハイフンまたはアンダースコアで始まるまたは終わることもできません。",
|
||||
"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\\\"",
|
||||
"Username already exists": "ユーザー名はすでに存在しています",
|
||||
"Username cannot be an email address": "ユーザー名には電子メールアドレスを使用できません",
|
||||
"Username cannot contain white spaces": "ユーザ名にはスペースを含めることはできません",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "소속은 비워 둘 수 없습니다",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName는 비어 있을 수 없습니다",
|
||||
"DisplayName is not valid real name": "DisplayName는 유효한 실제 이름이 아닙니다",
|
||||
"Email already exists": "이메일이 이미 존재합니다",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "빈 사용자 이름.",
|
||||
"FirstName cannot be blank": "이름은 공백일 수 없습니다",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
|
||||
"LastName cannot be blank": "성은 비어 있을 수 없습니다",
|
||||
"Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "전화기는 이미 존재합니다",
|
||||
"Phone cannot be empty": "전화는 비워 둘 수 없습니다",
|
||||
"Phone number is invalid": "전화번호가 유효하지 않습니다",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "세션이 만료되었습니다. 다시 로그인해주세요",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "사용자는 로그인이 금지되어 있습니다. 관리자에게 문의하십시오",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "사용자 이름은 알파벳, 숫자, 밑줄 또는 하이픈만 포함할 수 있으며, 연속된 하이픈 또는 밑줄을 가질 수 없으며, 하이픈 또는 밑줄로 시작하거나 끝날 수 없습니다.",
|
||||
"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\\\"",
|
||||
"Username already exists": "사용자 이름이 이미 존재합니다",
|
||||
"Username cannot be an email address": "사용자 이름은 이메일 주소가 될 수 없습니다",
|
||||
"Username cannot contain white spaces": "사용자 이름에는 공백이 포함될 수 없습니다",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -1,18 +1,18 @@
|
||||
{
|
||||
"account": {
|
||||
"Failed to add user": "Failed to add user",
|
||||
"Get init score failed, error: %w": "Get init score failed, error: %w",
|
||||
"Please sign out first": "Please sign out first",
|
||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
|
||||
"Failed to add user": "Falha ao adicionar usuário",
|
||||
"Get init score failed, error: %w": "Obter pontuação inicial falhou, erro: %w",
|
||||
"Please sign out first": "Por favor, saia da sessão primeiro",
|
||||
"The application does not allow to sign up new account": "O aplicativo não permite a criação de uma nova conta"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Challenge method should be S256",
|
||||
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
|
||||
"Failed to login in: %s": "Failed to login in: %s",
|
||||
"Invalid token": "Invalid token",
|
||||
"State expected: %s, but got: %s": "State expected: %s, but got: %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": "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",
|
||||
"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": "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",
|
||||
"Challenge method should be S256": "Método de desafio deve ser S256",
|
||||
"Failed to create user, user information is invalid: %s": "Falha ao criar usuário, informação do usuário inválida: %s",
|
||||
"Failed to login in: %s": "Falha ao entrar em: %s",
|
||||
"Invalid token": "Token inválido",
|
||||
"State expected: %s, but got: %s": "Estado esperado: %s, mas recebeu: %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": "A conta para o provedor: %s e nome de usuário: %s (%s) não existe e não é permitido inscrever-se como uma nova conta via %%s, por favor, use outra forma de se inscrever",
|
||||
"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": "A conta para o provedor: %s e nome de usuário: %s (%s) não existe e não é permitido inscrever-se como uma nova conta entre em contato com seu suporte de TI",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
|
||||
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,19 +49,24 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"Username cannot start with a digit": "O nome de usuário não pode começar com um dígito",
|
||||
"Username is too long (maximum is 39 characters).": "Nome de usuário é muito longo (máximo é 39 caracteres).",
|
||||
"Username must have at least 2 characters": "Nome de usuário deve ter pelo menos 2 caracteres",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect": "senha ou código incorreto",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
@ -82,15 +90,15 @@
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "Only admin can modify the %s.",
|
||||
"The %s is immutable.": "The %s is immutable.",
|
||||
"Unknown modify rule %s.": "Unknown modify rule %s."
|
||||
"The %s is immutable.": "O %s é imutável.",
|
||||
"Unknown modify rule %s.": "Regra de modificação %s desconhecida."
|
||||
},
|
||||
"provider": {
|
||||
"Invalid application id": "Invalid application id",
|
||||
"the provider: %s does not exist": "the provider: %s does not exist"
|
||||
"Invalid application id": "Id do aplicativo inválido",
|
||||
"the provider: %s does not exist": "o provedor: %s não existe"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "User is nil for tag: avatar",
|
||||
"User is nil for tag: avatar": "Usuário é nulo para tag: avatar",
|
||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
|
||||
},
|
||||
"saml": {
|
||||
@ -109,19 +117,19 @@
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported"
|
||||
},
|
||||
"token": {
|
||||
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
|
||||
"Empty clientId or clientSecret": "ClientId ou clientSecret vazio",
|
||||
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
|
||||
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
|
||||
"Invalid client_id": "Invalid client_id",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
|
||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||
"Invalid application or wrong clientSecret": "Aplicativo inválido ou clientSecret errado",
|
||||
"Invalid client_id": "client_id inválido",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "URI de redirecionamento: %s não existe na lista de URI de redirecionamento permitida",
|
||||
"Token not found, invalid accessToken": "Token não encontrado, token de acesso inválido"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Display name cannot be empty",
|
||||
"Display name cannot be empty": "Nome de exibição não pode ser vazio",
|
||||
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||
},
|
||||
"user_upload": {
|
||||
"Failed to import users": "Failed to import users"
|
||||
"Failed to import users": "Falha ao importar usuários"
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "No application is found for userId: %s",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"The application does not allow to sign up new account": "Приложение не позволяет зарегистрироваться новому аккаунту"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Метод испытаний должен быть S256",
|
||||
"Challenge method should be S256": "Метод проверки должен быть S256",
|
||||
"Failed to create user, user information is invalid: %s": "Не удалось создать пользователя, информация о пользователе недействительна: %s",
|
||||
"Failed to login in: %s": "Не удалось войти в систему: %s",
|
||||
"Invalid token": "Недействительный токен",
|
||||
@ -22,7 +22,7 @@
|
||||
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
|
||||
"Unauthorized operation": "Несанкционированная операция",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Неизвестный тип аутентификации (не пароль и не провайдер), форма = %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": "Тег пользователя: %s не указан в тэгах приложения",
|
||||
"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"
|
||||
},
|
||||
"cas": {
|
||||
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Принадлежность не может быть пустым значением",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "Имя отображения не может быть пустым",
|
||||
"DisplayName is not valid real name": "DisplayName не является действительным именем",
|
||||
"Email already exists": "Электронная почта уже существует",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Пустое имя пользователя.",
|
||||
"FirstName cannot be blank": "Имя не может быть пустым",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
|
||||
"LastName cannot be blank": "Фамилия не может быть пустой",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Телефон уже существует",
|
||||
"Phone cannot be empty": "Телефон не может быть пустым",
|
||||
"Phone number is invalid": "Номер телефона является недействительным",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the 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": "Пользователь %s не существует на 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.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Имя пользователя уже существует",
|
||||
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",
|
||||
"Username cannot contain white spaces": "Имя пользователя не может содержать пробелы",
|
||||
@ -58,7 +66,7 @@
|
||||
"Username must have at least 2 characters": "Имя пользователя должно содержать не менее 2 символов",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Вы ввели неправильный пароль или код слишком много раз, пожалуйста, подождите %d минут и попробуйте снова",
|
||||
"Your region is not allow to signup by phone": "Ваш регион не разрешает регистрацию по телефону",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect": "неправильный пароль или код",
|
||||
"password or code is incorrect, you have %d remaining chances": "Неправильный пароль или код, у вас осталось %d попыток",
|
||||
"unsupported password type: %s": "неподдерживаемый тип пароля: %s"
|
||||
},
|
||||
@ -66,8 +74,8 @@
|
||||
"Missing parameter": "Отсутствующий параметр",
|
||||
"Please login first": "Пожалуйста, сначала войдите в систему",
|
||||
"The user: %s doesn't exist": "Пользователь %s не существует",
|
||||
"don't support captchaProvider: ": "не поддерживайте captchaProvider:",
|
||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||
"don't support captchaProvider: ": "неподдерживаемый captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "эта операция не разрешена в демо-режиме"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "LDAP-сервер существует"
|
||||
@ -106,7 +114,7 @@
|
||||
},
|
||||
"storage": {
|
||||
"The objectKey: %s is not allowed": "Объект «objectKey: %s» не разрешен",
|
||||
"The provider type: %s is not supported": "Тип поставщика: %s не поддерживается"
|
||||
"The provider type: %s is not supported": "Тип провайдера: %s не поддерживается"
|
||||
},
|
||||
"token": {
|
||||
"Empty clientId or clientSecret": "Пустой идентификатор клиента или секрет клиента",
|
||||
@ -125,7 +133,7 @@
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "Не найдено заявки для пользователя с идентификатором: %s",
|
||||
"No provider for category: %s is found for application: %s": "Нет поставщика для категории: %s для приложения: %s",
|
||||
"No provider for category: %s is found for application: %s": "Нет провайдера для категории: %s для приложения: %s",
|
||||
"The provider: %s is not found": "Поставщик: %s не найден"
|
||||
},
|
||||
"verification": {
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,27 +39,34 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Phone already exists": "Telefon numarası zaten mevcut",
|
||||
"Phone cannot be empty": "Telefon numarası boş olamaz",
|
||||
"Phone number is invalid": "Telefon numarası geçersiz",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
"Username cannot start with a digit": "Username cannot start with a digit",
|
||||
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"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\\\"",
|
||||
"Username already exists": "Kullanıcı adı zaten var",
|
||||
"Username cannot be an email address": "Kullanıcı adı bir e-mail adresi olamaz",
|
||||
"Username cannot contain white spaces": "Kullanıcı adı boşluk karakteri içeremez",
|
||||
"Username cannot start with a digit": "Kullanıcı adı rakamla başlayamaz",
|
||||
"Username is too long (maximum is 39 characters).": "Kullanıcı adı çok uzun (en fazla 39 karakter olmalı).",
|
||||
"Username must have at least 2 characters": "Kullanıcı adı en az iki karakterden oluşmalı",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Çok fazla hatalı şifre denemesi yaptınız. %d dakika kadar bekleyip yeniden giriş yapmayı deneyebilirsiniz.",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect": "şifre veya kod hatalı",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
@ -117,8 +125,8 @@
|
||||
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Display name cannot be empty",
|
||||
"New password cannot contain blank space.": "New password cannot contain blank space."
|
||||
"Display name cannot be empty": "Görünen ad boş olamaz",
|
||||
"New password cannot contain blank space.": "Yeni şifreniz boşluk karakteri içeremez."
|
||||
},
|
||||
"user_upload": {
|
||||
"Failed to import users": "Failed to import users"
|
||||
@ -131,7 +139,7 @@
|
||||
"verification": {
|
||||
"Code has not been sent yet!": "Code has not been sent yet!",
|
||||
"Invalid captcha provider.": "Invalid captcha provider.",
|
||||
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
|
||||
"Phone number is invalid in your region %s": "Telefon numaranızın bulunduğu bölgeye hizmet veremiyoruz",
|
||||
"Turing test failed.": "Turing test failed.",
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
"DisplayName is not valid real name": "DisplayName is not valid real name",
|
||||
"Email already exists": "Email already exists",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "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.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
"Username cannot contain white spaces": "Username cannot contain white spaces",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Tình trạng liên kết không thể để trống",
|
||||
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
|
||||
"DisplayName cannot be blank": "Tên hiển thị không thể để trống",
|
||||
"DisplayName is not valid real name": "DisplayName không phải là tên thật hợp lệ",
|
||||
"Email already exists": "Email đã tồn tại",
|
||||
@ -38,7 +39,9 @@
|
||||
"Empty username.": "Tên đăng nhập trống.",
|
||||
"FirstName cannot be blank": "Tên không được để trống",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code exhausted": "Invitation code exhausted",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code suspended": "Invitation code suspended",
|
||||
"LDAP user name or password incorrect": "Tên người dùng hoặc mật khẩu Ldap không chính xác",
|
||||
"LastName cannot be blank": "Họ không thể để trống",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Nhiều tài khoản với cùng một uid, vui lòng kiểm tra máy chủ ldap của bạn",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "Điện thoại đã tồn tại",
|
||||
"Phone cannot be empty": "Điện thoại không thể để trống",
|
||||
"Phone number is invalid": "Số điện thoại không hợp lệ",
|
||||
"Please register using the email corresponding to the invitation code": "Please register using the email 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",
|
||||
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
|
||||
"Session outdated, please login again": "Phiên làm việc hết hạn, vui lòng đăng nhập lại",
|
||||
"The invitation code has already been used": "The invitation code has already been used",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Người dùng bị cấm đăng nhập, vui lòng liên hệ với quản trị viên",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"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.": "Tên người dùng chỉ có thể chứa các ký tự chữ và số, gạch dưới hoặc gạch ngang, không được có hai ký tự gạch dưới hoặc gạch ngang liền kề và không được bắt đầu hoặc kết thúc bằng dấu gạch dưới hoặc gạch ngang.",
|
||||
"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\\\"",
|
||||
"Username already exists": "Tên đăng nhập đã tồn tại",
|
||||
"Username cannot be an email address": "Tên người dùng không thể là địa chỉ email",
|
||||
"Username cannot contain white spaces": "Tên người dùng không thể chứa khoảng trắng",
|
||||
|
@ -30,6 +30,7 @@
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "工作单位不可为空",
|
||||
"Default code does not match the code's matching rules": "邀请码默认值和邀请码规则不匹配",
|
||||
"DisplayName cannot be blank": "显示名称不可为空",
|
||||
"DisplayName is not valid real name": "显示名称必须是真实姓名",
|
||||
"Email already exists": "该邮箱已存在",
|
||||
@ -37,8 +38,10 @@
|
||||
"Email is invalid": "无效邮箱",
|
||||
"Empty username.": "用户名不可为空",
|
||||
"FirstName cannot be blank": "名不可以为空",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"Invitation code cannot be blank": "邀请码不能为空",
|
||||
"Invitation code exhausted": "邀请码使用次数已耗尽",
|
||||
"Invitation code is invalid": "邀请码无效",
|
||||
"Invitation code suspended": "邀请码已被禁止使用",
|
||||
"LDAP user name or password incorrect": "LDAP密码错误",
|
||||
"LastName cannot be blank": "姓不可以为空",
|
||||
"Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid,请检查您的 LDAP 服务器",
|
||||
@ -46,10 +49,15 @@
|
||||
"Phone already exists": "该手机号已存在",
|
||||
"Phone cannot be empty": "手机号不可为空",
|
||||
"Phone number is invalid": "无效手机号",
|
||||
"Please register using the email corresponding to the invitation code": "请使用邀请码关联的邮箱注册",
|
||||
"Please register using the phone corresponding to the invitation code": "请使用邀请码关联的手机号注册",
|
||||
"Please register using the username corresponding to the invitation code": "请使用邀请码关联的用户名注册",
|
||||
"Session outdated, please login again": "会话已过期,请重新登录",
|
||||
"The invitation code has already been used": "邀请码已被使用",
|
||||
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
|
||||
"The user: %s doesn't exist in LDAP server": "用户: %s 在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.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
|
||||
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "值\\\"%s\\\"在注册字段\\\"%s\\\"中与应用\\\"%s\\\"的注册项正则表达式不匹配",
|
||||
"Username already exists": "用户名已存在",
|
||||
"Username cannot be an email address": "用户名不可以是邮箱地址",
|
||||
"Username cannot contain white spaces": "用户名禁止包含空格",
|
||||
|
@ -121,6 +121,9 @@ func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var respKeys struct {
|
||||
Keys []interface{} `json:"keys"`
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ import (
|
||||
"github.com/markbates/goth/providers/twitterv2"
|
||||
"github.com/markbates/goth/providers/typetalk"
|
||||
"github.com/markbates/goth/providers/uber"
|
||||
"github.com/markbates/goth/providers/vk"
|
||||
"github.com/markbates/goth/providers/wepay"
|
||||
"github.com/markbates/goth/providers/xero"
|
||||
"github.com/markbates/goth/providers/yahoo"
|
||||
@ -371,6 +372,11 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
||||
Provider: uber.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &uber.Session{},
|
||||
}
|
||||
case "VK":
|
||||
idp = GothIdProvider{
|
||||
Provider: vk.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &vk.Session{},
|
||||
}
|
||||
case "Wepay":
|
||||
idp = GothIdProvider{
|
||||
Provider: wepay.New(clientId, clientSecret, redirectUrl),
|
||||
|
30
idp/lark.go
30
idp/lark.go
@ -16,6 +16,7 @@ package idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -82,13 +83,22 @@ func (idp *LarkIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
AppID string `json:"app_id"`
|
||||
AppSecret string `json:"app_secret"`
|
||||
}{idp.Config.ClientID, idp.Config.ClientSecret}
|
||||
|
||||
data, err := idp.postWithBody(params, idp.Config.Endpoint.TokenURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appToken := &LarkAccessToken{}
|
||||
if err = json.Unmarshal(data, appToken); err != nil || appToken.Code != 0 {
|
||||
err = json.Unmarshal(data, appToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if appToken.Code != 0 {
|
||||
return nil, fmt.Errorf("GetToken() error, appToken.Code: %d, appToken.Msg: %s", appToken.Code, appToken.Msg)
|
||||
}
|
||||
|
||||
t := &oauth2.Token{
|
||||
AccessToken: appToken.TenantAccessToken,
|
||||
TokenType: "Bearer",
|
||||
@ -98,7 +108,6 @@ func (idp *LarkIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
raw := make(map[string]interface{})
|
||||
raw["code"] = code
|
||||
t = t.WithExtra(raw)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
@ -159,11 +168,17 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
}{"authorization_code", token.Extra("code").(string)}
|
||||
data, _ := json.Marshal(body)
|
||||
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://open.feishu.cn/open-apis/authen/v1/access_token", strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
||||
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
||||
|
||||
@ -171,6 +186,7 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
@ -178,7 +194,8 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
}
|
||||
|
||||
var larkUserInfo LarkUserInfo
|
||||
if err = json.Unmarshal(data, &larkUserInfo); err != nil {
|
||||
err = json.Unmarshal(data, &larkUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -189,7 +206,6 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
Email: larkUserInfo.Data.Email,
|
||||
AvatarUrl: larkUserInfo.Data.AvatarUrl,
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
@ -198,21 +214,23 @@ func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := strings.NewReader(string(bs))
|
||||
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
@ -119,6 +119,8 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error
|
||||
return NewMetaMaskIdProvider(), nil
|
||||
case "Web3Onboard":
|
||||
return NewWeb3OnboardIdProvider(), nil
|
||||
case "Twitter":
|
||||
return NewTwitterIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
default:
|
||||
if isGothSupport(idpInfo.Type) {
|
||||
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.ClientId2, idpInfo.ClientSecret2, redirectUrl, idpInfo.HostUrl)
|
||||
@ -171,7 +173,6 @@ var gothList = []string{
|
||||
"TikTok",
|
||||
"Tumblr",
|
||||
"Twitch",
|
||||
"Twitter",
|
||||
"Typetalk",
|
||||
"Uber",
|
||||
"VK",
|
||||
|
190
idp/twitter.go
Normal file
190
idp/twitter.go
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright 2021 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 idp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type TwitterIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewTwitterIdProvider(clientId string, clientSecret string, redirectUrl string) *TwitterIdProvider {
|
||||
idp := &TwitterIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *TwitterIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *TwitterIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
endpoint := oauth2.Endpoint{
|
||||
TokenURL: "https://api.twitter.com/2/oauth2/token",
|
||||
}
|
||||
|
||||
config := &oauth2.Config{
|
||||
Scopes: []string{"users.read", "tweet.read"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type TwitterAccessToken struct {
|
||||
AccessToken string `json:"access_token"` // Interface call credentials
|
||||
TokenType string `json:"token_type"` // Access token type
|
||||
ExpiresIn int64 `json:"expires_in"` // access_token interface call credential timeout time, unit (seconds)
|
||||
}
|
||||
|
||||
type TwitterCheckToken struct {
|
||||
Data TwitterUserInfo `json:"data"`
|
||||
}
|
||||
|
||||
// TwitterCheckTokenData
|
||||
// Get more detail via: https://developers.Twitter.com/docs/Twitter-login/guides/advanced/manual-flow#checktoken
|
||||
type TwitterCheckTokenData struct {
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.Twitter.com/docs/Twitter-login/guides/advanced/manual-flow#confirm
|
||||
func (idp *TwitterIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
// params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
params.Add("code_verifier", "casdoor-verifier")
|
||||
params.Add("code", code)
|
||||
params.Add("grant_type", "authorization_code")
|
||||
req, err := http.NewRequest("POST", "https://api.twitter.com/2/oauth2/token", strings.NewReader(params.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
e := base64.StdEncoding.EncodeToString([]byte(idp.Config.ClientID + ":" + idp.Config.ClientSecret))
|
||||
req.Header.Add("Authorization", "Basic "+e)
|
||||
accessTokenResp, err := idp.GetUrlResp(req)
|
||||
var TwitterAccessToken TwitterAccessToken
|
||||
if err = json.Unmarshal([]byte(accessTokenResp), &TwitterAccessToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := oauth2.Token{
|
||||
AccessToken: TwitterAccessToken.AccessToken,
|
||||
TokenType: TwitterAccessToken.TokenType,
|
||||
Expiry: time.Time{},
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
//{
|
||||
// "id": "123456789",
|
||||
// "name": "Example Name",
|
||||
// "name_format": "{first} {last}",
|
||||
// "picture": {
|
||||
// "data": {
|
||||
// "height": 50,
|
||||
// "is_silhouette": false,
|
||||
// "url": "https://example.com",
|
||||
// "width": 50
|
||||
// }
|
||||
// },
|
||||
// "email": "test@example.com"
|
||||
//}
|
||||
|
||||
type TwitterUserInfo struct {
|
||||
Id string `json:"id"` // The app user's App-Scoped User ID. This ID is unique to the app and cannot be used by other apps.
|
||||
Name string `json:"name"` // The person's full name.
|
||||
UserName string `json:"username"` // The person's name formatted to correctly handle Chinese, Japanese, or Korean ordering.
|
||||
Picture struct { // The person's profile picture.
|
||||
Data struct { // This struct is different as https://developers.Twitter.com/docs/graph-api/reference/user/picture/
|
||||
Height int `json:"height"`
|
||||
IsSilhouette bool `json:"is_silhouette"`
|
||||
Url string `json:"url"`
|
||||
Width int `json:"width"`
|
||||
} `json:"data"`
|
||||
} `json:"picture"`
|
||||
Email string `json:"email"` // The User's primary email address listed on their profile. This field will not be returned if no valid email address is available.
|
||||
}
|
||||
|
||||
// GetUserInfo use TwitterAccessToken gotten before return TwitterUserInfo
|
||||
// get more detail via: https://developers.Twitter.com/docs/graph-api/reference/user
|
||||
func (idp *TwitterIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var TwitterUserInfo TwitterUserInfo
|
||||
// accessToken := token.AccessToken
|
||||
|
||||
req, err := http.NewRequest("GET", "https://api.twitter.com/2/users/me", nil)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Add("Authorization", "Bearer "+token.AccessToken)
|
||||
// req.URL.Query().Set("user.fields", "profile_image_url")
|
||||
// userIdUrl := fmt.Sprintf("https://graph.Twitter.com/me?access_token=%s", accessToken)
|
||||
userIdResp, err := idp.GetUrlResp(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
empTwitterCheckToken := &TwitterCheckToken{}
|
||||
if err = json.Unmarshal([]byte(userIdResp), &empTwitterCheckToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
TwitterUserInfo = empTwitterCheckToken.Data
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: TwitterUserInfo.Id,
|
||||
Username: TwitterUserInfo.UserName,
|
||||
DisplayName: TwitterUserInfo.Name,
|
||||
Email: TwitterUserInfo.Email,
|
||||
AvatarUrl: TwitterUserInfo.Picture.Data.Url,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *TwitterIdProvider) GetUrlResp(url *http.Request) (string, error) {
|
||||
resp, err := idp.Client.Do(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
104
idp/wechat.go
104
idp/wechat.go
@ -16,25 +16,38 @@ package idp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/skip2/go-qrcode"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
WechatCacheMap map[string]WechatCacheMapValue
|
||||
Lock sync.RWMutex
|
||||
)
|
||||
|
||||
type WeChatIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
type WechatCacheMapValue struct {
|
||||
IsScanned bool
|
||||
WechatUnionId string
|
||||
}
|
||||
|
||||
func NewWeChatIdProvider(clientId string, clientSecret string, redirectUrl string) *WeChatIdProvider {
|
||||
idp := &WeChatIdProvider{}
|
||||
|
||||
@ -77,6 +90,15 @@ type WechatAccessToken struct {
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
|
||||
func (idp *WeChatIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
if strings.HasPrefix(code, "wechat_oa:") {
|
||||
token := oauth2.Token{
|
||||
AccessToken: code,
|
||||
TokenType: "WeChatAccessToken",
|
||||
Expiry: time.Time{},
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("appid", idp.Config.ClientID)
|
||||
@ -157,6 +179,29 @@ type WechatUserInfo struct {
|
||||
func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var wechatUserInfo WechatUserInfo
|
||||
accessToken := token.AccessToken
|
||||
|
||||
if strings.HasPrefix(accessToken, "wechat_oa:") {
|
||||
Lock.RLock()
|
||||
mapValue, ok := WechatCacheMap[accessToken[10:]]
|
||||
Lock.RUnlock()
|
||||
|
||||
if !ok || mapValue.WechatUnionId == "" {
|
||||
return nil, fmt.Errorf("error ticket")
|
||||
}
|
||||
|
||||
Lock.Lock()
|
||||
delete(WechatCacheMap, accessToken[10:])
|
||||
Lock.Unlock()
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: mapValue.WechatUnionId,
|
||||
Username: "wx_user_" + mapValue.WechatUnionId,
|
||||
DisplayName: "wx_user_" + mapValue.WechatUnionId,
|
||||
AvatarUrl: "",
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
openid := token.Extra("Openid")
|
||||
|
||||
userInfoUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", accessToken, openid)
|
||||
@ -204,60 +249,70 @@ func BuildWechatOpenIdKey(appId string) string {
|
||||
return fmt.Sprintf("wechat_openid_%s", appId)
|
||||
}
|
||||
|
||||
func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) {
|
||||
func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, string, error) {
|
||||
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret)
|
||||
request, err := http.NewRequest("GET", accessTokenUrl, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
client := new(http.Client)
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var data struct {
|
||||
ExpireIn int `json:"expires_in"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ErrCode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
}
|
||||
err = json.Unmarshal(respBytes, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return data.AccessToken, nil
|
||||
return data.AccessToken, data.Errmsg, nil
|
||||
}
|
||||
|
||||
func GetWechatOfficialAccountQRCode(clientId string, clientSecret string) (string, error) {
|
||||
accessToken, err := GetWechatOfficialAccountAccessToken(clientId, clientSecret)
|
||||
func GetWechatOfficialAccountQRCode(clientId string, clientSecret string, providerId string) (string, string, error) {
|
||||
accessToken, errMsg, err := GetWechatOfficialAccountAccessToken(clientId, clientSecret)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if errMsg != "" {
|
||||
return "", "", fmt.Errorf("Fail to fetch WeChat QRcode: %s", errMsg)
|
||||
}
|
||||
|
||||
client := new(http.Client)
|
||||
|
||||
weChatEndpoint := "https://api.weixin.qq.com/cgi-bin/qrcode/create"
|
||||
qrCodeUrl := fmt.Sprintf("%s?access_token=%s", weChatEndpoint, accessToken)
|
||||
params := `{"action_name": "QR_LIMIT_STR_SCENE", "action_info": {"scene": {"scene_str": "test"}}}`
|
||||
params := fmt.Sprintf(`{"expire_seconds": 3600, "action_name": "QR_STR_SCENE", "action_info": {"scene": {"scene_str": "%s"}}}`, providerId)
|
||||
|
||||
bodyData := bytes.NewReader([]byte(params))
|
||||
requeset, err := http.NewRequest("POST", qrCodeUrl, bodyData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
resp, err := client.Do(requeset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
var data struct {
|
||||
Ticket string `json:"ticket"`
|
||||
@ -266,11 +321,26 @@ func GetWechatOfficialAccountQRCode(clientId string, clientSecret string) (strin
|
||||
}
|
||||
err = json.Unmarshal(respBytes, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var png []byte
|
||||
png, err = qrcode.Encode(data.URL, qrcode.Medium, 256)
|
||||
base64Image := base64.StdEncoding.EncodeToString(png)
|
||||
return base64Image, nil
|
||||
return base64Image, data.Ticket, nil
|
||||
}
|
||||
|
||||
func VerifyWechatSignature(token string, nonce string, timestamp string, signature string) bool {
|
||||
// verify the signature
|
||||
tmpArr := sort.StringSlice{token, timestamp, nonce}
|
||||
sort.Sort(tmpArr)
|
||||
|
||||
tmpStr := ""
|
||||
for _, str := range tmpArr {
|
||||
tmpStr = tmpStr + str
|
||||
}
|
||||
|
||||
b := sha1.Sum([]byte(tmpStr))
|
||||
res := hex.EncodeToString(b[:])
|
||||
return res == signature
|
||||
}
|
||||
|
@ -123,7 +123,7 @@
|
||||
"redirectUris": [""],
|
||||
"expireInHours": 168,
|
||||
"failedSigninLimit": 5,
|
||||
"failedSigninfrozenTime": 15
|
||||
"failedSigninFrozenTime": 15
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
@ -146,7 +146,8 @@
|
||||
"isForbidden": false,
|
||||
"isDeleted": false,
|
||||
"signupApplication": "",
|
||||
"createdIp": ""
|
||||
"createdIp": "",
|
||||
"groups": []
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
@ -349,5 +350,74 @@
|
||||
"owner": "",
|
||||
"url": ""
|
||||
}
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"owner": "",
|
||||
"name":"",
|
||||
"displayName": "",
|
||||
"manager": "",
|
||||
"contactEmail": "",
|
||||
"type": "",
|
||||
"parent_id": "",
|
||||
"isTopGroup": true,
|
||||
"title": "",
|
||||
"key": "",
|
||||
"children": "",
|
||||
"isEnabled": true
|
||||
}
|
||||
],
|
||||
"adapters": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"table": "",
|
||||
"useSameDb": true,
|
||||
"type": "",
|
||||
"databaseType": "",
|
||||
"database": "",
|
||||
"host": "",
|
||||
"port": 0,
|
||||
"user": "",
|
||||
"password": "",
|
||||
}
|
||||
],
|
||||
"enforcers": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"description": "",
|
||||
"model": "",
|
||||
"adapter": "",
|
||||
"enforcer": ""
|
||||
}
|
||||
],
|
||||
"plans": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"description": "",
|
||||
"price": 0,
|
||||
"currency": "",
|
||||
"period": "",
|
||||
"product": "",
|
||||
"paymentProviders": [],
|
||||
"isEnabled": true,
|
||||
"role", ""
|
||||
}
|
||||
],
|
||||
"pricings": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"description": "",
|
||||
"plans": [],
|
||||
"isEnabled": true,
|
||||
"trialDuration": 0,
|
||||
"application": "",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
149
ldap/server.go
149
ldap/server.go
@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -50,17 +49,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
res := ldap.NewBindResponse(ldap.LDAPResultSuccess)
|
||||
|
||||
if r.AuthenticationChoice() == "simple" {
|
||||
bindDN := string(r.Name())
|
||||
bindPassword := string(r.AuthenticationSimple())
|
||||
|
||||
if bindDN == "" && bindPassword == "" {
|
||||
res.SetResultCode(ldap.LDAPResultInappropriateAuthentication)
|
||||
res.SetDiagnosticMessage("Anonymous bind disallowed")
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
bindUsername, bindOrg, err := getNameAndOrgFromDN(bindDN)
|
||||
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
|
||||
if err != nil {
|
||||
log.Printf("getNameAndOrgFromDN() error: %s", err.Error())
|
||||
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
|
||||
@ -69,6 +58,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
bindPassword := string(r.AuthenticationSimple())
|
||||
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
|
||||
if err != nil {
|
||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||
@ -103,46 +93,7 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
}
|
||||
|
||||
r := m.GetSearchRequest()
|
||||
|
||||
// case insensitive match
|
||||
if strings.EqualFold(r.FilterString(), "(objectClass=*)") {
|
||||
if len(r.Attributes()) == 0 {
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
first_attr := string(r.Attributes()[0])
|
||||
|
||||
if string(r.BaseObject()) == "" {
|
||||
// handle special search requests
|
||||
|
||||
if first_attr == "namingContexts" {
|
||||
orgs, code := GetFilteredOrganizations(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
e := ldap.NewSearchResultEntry(string(r.BaseObject()))
|
||||
dnlist := make([]message.AttributeValue, len(orgs))
|
||||
for i, org := range orgs {
|
||||
dnlist[i] = message.AttributeValue(fmt.Sprintf("ou=%s", org.Name))
|
||||
}
|
||||
e.AddAttribute("namingContexts", dnlist...)
|
||||
w.Write(e)
|
||||
} else if first_attr == "subschemaSubentry" {
|
||||
e := ldap.NewSearchResultEntry(string(r.BaseObject()))
|
||||
e.AddAttribute("subschemaSubentry", message.AttributeValue("cn=Subschema"))
|
||||
w.Write(e)
|
||||
}
|
||||
} else if strings.EqualFold(first_attr, "objectclasses") && string(r.BaseObject()) == "cn=Subschema" {
|
||||
e := ldap.NewSearchResultEntry(string(r.BaseObject()))
|
||||
e.AddAttribute("objectClasses", []message.AttributeValue{
|
||||
"( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) )",
|
||||
"( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPassword $ memberUid $ description ) )",
|
||||
}...)
|
||||
w.Write(e)
|
||||
}
|
||||
|
||||
if r.FilterString() == "(objectClass=*)" {
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
@ -155,72 +106,38 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
default:
|
||||
}
|
||||
|
||||
objectClass := searchFilterForEquality(r.Filter(), "objectClass", "posixAccount", "posixGroup")
|
||||
switch objectClass {
|
||||
case "posixAccount":
|
||||
users, code := GetFilteredUsers(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
// log.Printf("Handling posixAccount filter=%s", r.FilterString())
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("uid=%s,cn=users,%s", user.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
attrs := r.Attributes()
|
||||
for _, attr := range attrs {
|
||||
if string(attr) == "*" {
|
||||
attrs = AdditionalLdapUserAttributes
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
if strings.HasSuffix(string(attr), ";binary") {
|
||||
// unsupported: userCertificate;binary
|
||||
continue
|
||||
}
|
||||
field, ok := ldapUserAttributesMapping.CaseInsensitiveGet(string(attr))
|
||||
if ok {
|
||||
e.AddAttribute(message.AttributeDescription(attr), field.GetAttributeValues(user)...)
|
||||
}
|
||||
}
|
||||
w.Write(e)
|
||||
}
|
||||
|
||||
case "posixGroup":
|
||||
// log.Printf("Handling posixGroup filter=%s", r.FilterString())
|
||||
groups, code := GetFilteredGroups(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
dn := fmt.Sprintf("cn=%s,cn=groups,%s", group.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
attrs := r.Attributes()
|
||||
for _, attr := range attrs {
|
||||
if string(attr) == "*" {
|
||||
attrs = AdditionalLdapGroupAttributes
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
field, ok := ldapGroupAttributesMapping.CaseInsensitiveGet(string(attr))
|
||||
if ok {
|
||||
e.AddAttribute(message.AttributeDescription(attr), field.GetAttributeValues(group)...)
|
||||
}
|
||||
}
|
||||
w.Write(e)
|
||||
}
|
||||
|
||||
case "":
|
||||
log.Printf("Unmatched search request. filter=%s", r.FilterString())
|
||||
users, code := GetFilteredUsers(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("uid=%s,cn=%s,%s", user.Id, user.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
uidNumberStr := fmt.Sprintf("%v", hash(user.Name))
|
||||
e.AddAttribute("uidNumber", message.AttributeValue(uidNumberStr))
|
||||
e.AddAttribute("gidNumber", message.AttributeValue(uidNumberStr))
|
||||
e.AddAttribute("homeDirectory", message.AttributeValue("/home/"+user.Name))
|
||||
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||
e.AddAttribute("uid", message.AttributeValue(user.Id))
|
||||
attrs := r.Attributes()
|
||||
for _, attr := range attrs {
|
||||
if string(attr) == "*" {
|
||||
attrs = AdditionalLdapAttributes
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
||||
if string(attr) == "cn" {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
||||
}
|
||||
}
|
||||
|
||||
w.Write(e)
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
|
342
ldap/util.go
342
ldap/util.go
@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -29,259 +28,65 @@ import (
|
||||
"github.com/xorm-io/builder"
|
||||
)
|
||||
|
||||
type V = message.AttributeValue
|
||||
type AttributeMapper func(user *object.User) message.AttributeValue
|
||||
|
||||
type UserAttributeMapper func(user *object.User) []V
|
||||
|
||||
type UserFieldRelation struct {
|
||||
type FieldRelation struct {
|
||||
userField string
|
||||
ldapField string
|
||||
notSearchable bool
|
||||
hideOnStarOp bool
|
||||
fieldMapper UserAttributeMapper
|
||||
constantValue []V
|
||||
fieldMapper AttributeMapper
|
||||
}
|
||||
|
||||
func (rel UserFieldRelation) GetField() (string, error) {
|
||||
func (rel FieldRelation) GetField() (string, error) {
|
||||
if rel.notSearchable {
|
||||
return "", fmt.Errorf("attribute %s not supported", rel.userField)
|
||||
}
|
||||
return rel.userField, nil
|
||||
}
|
||||
|
||||
func (rel UserFieldRelation) GetAttributeValues(user *object.User) []V {
|
||||
if rel.constantValue != nil && rel.fieldMapper == nil {
|
||||
return rel.constantValue
|
||||
}
|
||||
func (rel FieldRelation) GetAttributeValue(user *object.User) message.AttributeValue {
|
||||
return rel.fieldMapper(user)
|
||||
}
|
||||
|
||||
type UserFieldRelationMap map[string]UserFieldRelation
|
||||
|
||||
func (m UserFieldRelationMap) CaseInsensitiveGet(key string) (UserFieldRelation, bool) {
|
||||
lowerKey := strings.ToLower(key)
|
||||
ret, ok := m[lowerKey]
|
||||
return ret, ok
|
||||
}
|
||||
|
||||
type GroupAttributeMapper func(group *object.Group) []V
|
||||
|
||||
type GroupFieldRelation struct {
|
||||
groupField string
|
||||
ldapField string
|
||||
notSearchable bool
|
||||
hideOnStarOp bool
|
||||
fieldMapper GroupAttributeMapper
|
||||
constantValue []V
|
||||
}
|
||||
|
||||
func (rel GroupFieldRelation) GetField() (string, error) {
|
||||
if rel.notSearchable {
|
||||
return "", fmt.Errorf("attribute %s not supported", rel.groupField)
|
||||
}
|
||||
return rel.groupField, nil
|
||||
}
|
||||
|
||||
func (rel GroupFieldRelation) GetAttributeValues(group *object.Group) []V {
|
||||
if rel.constantValue != nil && rel.fieldMapper == nil {
|
||||
return rel.constantValue
|
||||
}
|
||||
return rel.fieldMapper(group)
|
||||
}
|
||||
|
||||
type GroupFieldRelationMap map[string]GroupFieldRelation
|
||||
|
||||
func (m GroupFieldRelationMap) CaseInsensitiveGet(key string) (GroupFieldRelation, bool) {
|
||||
lowerKey := strings.ToLower(key)
|
||||
ret, ok := m[lowerKey]
|
||||
return ret, ok
|
||||
}
|
||||
|
||||
var ldapUserAttributesMapping = UserFieldRelationMap{
|
||||
"cn": {ldapField: "cn", userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Name)}
|
||||
var ldapAttributesMapping = map[string]FieldRelation{
|
||||
"cn": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Name)
|
||||
}},
|
||||
"uid": {ldapField: "uid", userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Name)}
|
||||
"uid": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Name)
|
||||
}},
|
||||
"displayname": {ldapField: "displayName", userField: "displayName", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.DisplayName)}
|
||||
"displayname": {userField: "displayName", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.DisplayName)
|
||||
}},
|
||||
"email": {ldapField: "email", userField: "email", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Email)}
|
||||
"email": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Email)
|
||||
}},
|
||||
"mail": {ldapField: "mail", userField: "email", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Email)}
|
||||
"mail": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Email)
|
||||
}},
|
||||
"mobile": {ldapField: "mobile", userField: "phone", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Phone)}
|
||||
"mobile": {userField: "phone", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Phone)
|
||||
}},
|
||||
"telephonenumber": {ldapField: "telephoneNumber", userField: "phone", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Phone)}
|
||||
"title": {userField: "tag", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Tag)
|
||||
}},
|
||||
"postaladdress": {ldapField: "postalAddress", userField: "address", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(strings.Join(user.Address, " "))}
|
||||
}},
|
||||
"title": {ldapField: "title", userField: "title", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Title)}
|
||||
}},
|
||||
"gecos": {ldapField: "gecos", userField: "displayName", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.DisplayName)}
|
||||
}},
|
||||
"description": {ldapField: "description", userField: "displayName", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.DisplayName)}
|
||||
}},
|
||||
"logindisabled": {ldapField: "loginDisabled", userField: "isForbidden", fieldMapper: func(user *object.User) []V {
|
||||
if user.IsForbidden {
|
||||
return []V{V("1")}
|
||||
} else {
|
||||
return []V{V("0")}
|
||||
}
|
||||
}},
|
||||
"userpassword": {
|
||||
ldapField: "userPassword",
|
||||
"userPassword": {
|
||||
userField: "userPassword",
|
||||
notSearchable: true,
|
||||
fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(getUserPasswordWithType(user))}
|
||||
fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(getUserPasswordWithType(user))
|
||||
},
|
||||
},
|
||||
"uidnumber": {ldapField: "uidNumber", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(fmt.Sprintf("%v", hash(user.Name)))}
|
||||
}},
|
||||
"gidnumber": {ldapField: "gidNumber", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
if len(user.Groups) == 0 {
|
||||
return []V{V("")}
|
||||
}
|
||||
group, err := object.GetGroup(user.Groups[0])
|
||||
if err != nil {
|
||||
log.Printf("gidnumber object.GetGroup error: %s", err)
|
||||
return []V{V("")}
|
||||
}
|
||||
return []V{V(fmt.Sprintf("%v", hash(group.Name)))}
|
||||
}},
|
||||
"homedirectory": {ldapField: "homeDirectory", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
return []V{V("/home/" + user.Name)}
|
||||
}},
|
||||
"loginshell": {ldapField: "loginShell", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
if user.IsForbidden || user.IsDeleted {
|
||||
return []V{V("/sbin/nologin")}
|
||||
} else {
|
||||
return []V{V("/bin/bash")}
|
||||
}
|
||||
}},
|
||||
"shadowlastchange": {ldapField: "shadowLastChange", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
// "this attribute specifies number of days between January 1, 1970, and the date that the password was last modified"
|
||||
updatedTime, err := time.Parse(time.RFC3339, user.UpdatedTime)
|
||||
if err != nil {
|
||||
log.Printf("shadowlastchange time.Parse error: %s", err)
|
||||
updatedTime = time.Now()
|
||||
}
|
||||
return []V{V(fmt.Sprint(updatedTime.Unix() / 86400))}
|
||||
}},
|
||||
"pwdchangedtime": {ldapField: "pwdChangedTime", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
updatedTime, err := time.Parse(time.RFC3339, user.UpdatedTime)
|
||||
if err != nil {
|
||||
log.Printf("pwdchangedtime time.Parse error: %s", err)
|
||||
updatedTime = time.Now()
|
||||
}
|
||||
return []V{V(updatedTime.UTC().Format("20060102030405Z"))}
|
||||
}},
|
||||
"shadowmin": {ldapField: "shadowMin", notSearchable: true, constantValue: []V{V("0")}},
|
||||
"shadowmax": {ldapField: "shadowMax", notSearchable: true, constantValue: []V{V("99999")}},
|
||||
"shadowwarning": {ldapField: "shadowWarning", notSearchable: true, constantValue: []V{V("7")}},
|
||||
"shadowexpire": {ldapField: "shadowExpire", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
if user.IsForbidden {
|
||||
return []V{V("1")}
|
||||
} else {
|
||||
return []V{V("-1")}
|
||||
}
|
||||
}},
|
||||
"shadowinactive": {ldapField: "shadowInactive", notSearchable: true, constantValue: []V{V("0")}},
|
||||
"shadowflag": {ldapField: "shadowFlag", notSearchable: true, constantValue: []V{V("0")}},
|
||||
"memberof": {ldapField: "memberOf", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
var groupdn []V
|
||||
for _, groupId := range user.Groups {
|
||||
group, err := object.GetGroup(groupId)
|
||||
if err != nil {
|
||||
log.Printf("memberOf object.GetGroup error: %s", err)
|
||||
continue
|
||||
}
|
||||
groupdn = append(groupdn, V(fmt.Sprintf("cn=%s,cn=groups,ou=%s", group.Name, group.Owner)))
|
||||
}
|
||||
return groupdn
|
||||
}},
|
||||
"objectclass": {ldapField: "objectClass", notSearchable: true, constantValue: []V{
|
||||
V("top"),
|
||||
V("posixAccount"),
|
||||
V("shadowAccount"),
|
||||
V("person"),
|
||||
V("organizationalPerson"),
|
||||
V("inetOrgPerson"),
|
||||
V("apple-user"),
|
||||
V("sambaSamAccount"),
|
||||
V("sambaIdmapEntry"),
|
||||
V("extensibleObject"),
|
||||
}},
|
||||
}
|
||||
|
||||
var ldapGroupAttributesMapping = GroupFieldRelationMap{
|
||||
"cn": {ldapField: "cn", hideOnStarOp: true, fieldMapper: func(group *object.Group) []V {
|
||||
return []V{V(group.Name)}
|
||||
}},
|
||||
"gidnumber": {ldapField: "gidNumber", hideOnStarOp: true, fieldMapper: func(group *object.Group) []V {
|
||||
return []V{V(fmt.Sprintf("%v", hash(group.Name)))}
|
||||
}},
|
||||
"member": {ldapField: "member", fieldMapper: func(group *object.Group) []V {
|
||||
users, err := object.GetGroupUsers(group.GetId())
|
||||
if err != nil {
|
||||
log.Printf("member object.GetGroupUsers error: %s", err)
|
||||
return []V{V("")}
|
||||
}
|
||||
var members []V
|
||||
for _, user := range users {
|
||||
members = append(members, V(fmt.Sprintf("uid=%s,cn=users,ou=%s", user.Name, user.Owner)))
|
||||
}
|
||||
return members
|
||||
}},
|
||||
"memberuid": {ldapField: "memberUid", fieldMapper: func(group *object.Group) []V {
|
||||
users, err := object.GetGroupUsers(group.GetId())
|
||||
if err != nil {
|
||||
log.Printf("member object.GetGroupUsers error: %s", err)
|
||||
return []V{V("")}
|
||||
}
|
||||
var members []message.AttributeValue
|
||||
for _, user := range users {
|
||||
members = append(members, message.AttributeValue(user.Name))
|
||||
}
|
||||
return members
|
||||
}},
|
||||
"description": {ldapField: "description", hideOnStarOp: true, fieldMapper: func(group *object.Group) []V {
|
||||
return []V{V(group.DisplayName)}
|
||||
}},
|
||||
"objectclass": {ldapField: "objectClass", hideOnStarOp: true, constantValue: []V{
|
||||
V("top"),
|
||||
V("posixGroup"),
|
||||
}},
|
||||
}
|
||||
|
||||
var (
|
||||
AdditionalLdapUserAttributes []message.LDAPString
|
||||
AdditionalLdapGroupAttributes []message.LDAPString
|
||||
)
|
||||
var AdditionalLdapAttributes []message.LDAPString
|
||||
|
||||
func init() {
|
||||
for _, v := range ldapUserAttributesMapping {
|
||||
for k, v := range ldapAttributesMapping {
|
||||
if v.hideOnStarOp {
|
||||
continue
|
||||
}
|
||||
AdditionalLdapUserAttributes = append(AdditionalLdapUserAttributes, message.LDAPString(v.ldapField))
|
||||
}
|
||||
for _, v := range ldapGroupAttributesMapping {
|
||||
if v.hideOnStarOp {
|
||||
continue
|
||||
}
|
||||
AdditionalLdapGroupAttributes = append(AdditionalLdapGroupAttributes, message.LDAPString(v.ldapField))
|
||||
AdditionalLdapAttributes = append(AdditionalLdapAttributes, message.LDAPString(k))
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,52 +307,6 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
}
|
||||
}
|
||||
|
||||
func GetFilteredOrganizations(m *ldap.Message) ([]*object.Organization, int) {
|
||||
if m.Client.IsGlobalAdmin {
|
||||
organizations, err := object.GetOrganizations("")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return organizations, ldap.LDAPResultSuccess
|
||||
} else if m.Client.IsOrgAdmin {
|
||||
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
|
||||
user, err := object.GetUser(requestUserId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
organization, err := object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return []*object.Organization{organization}, ldap.LDAPResultSuccess
|
||||
} else {
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
}
|
||||
|
||||
func GetFilteredGroups(m *ldap.Message) ([]*object.Group, int) {
|
||||
if m.Client.IsGlobalAdmin {
|
||||
groups, err := object.GetGroups("")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return groups, ldap.LDAPResultSuccess
|
||||
} else if m.Client.IsOrgAdmin {
|
||||
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
|
||||
user, err := object.GetUser(requestUserId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
groups, err := object.GetGroups(user.Owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return groups, ldap.LDAPResultSuccess
|
||||
} else {
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
}
|
||||
|
||||
// get user password with hash type prefix
|
||||
// TODO not handle salt yet
|
||||
// @return {md5}5f4dcc3b5aa765d61d8327deb882cf99
|
||||
@ -571,49 +330,18 @@ func getUserPasswordWithType(user *object.User) string {
|
||||
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
||||
}
|
||||
|
||||
func getAttribute(attributeName string, user *object.User) message.AttributeValue {
|
||||
v, ok := ldapAttributesMapping[attributeName]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return v.GetAttributeValue(user)
|
||||
}
|
||||
|
||||
func getUserFieldFromAttribute(attributeName string) (string, error) {
|
||||
v, ok := ldapUserAttributesMapping.CaseInsensitiveGet(attributeName)
|
||||
v, ok := ldapAttributesMapping[attributeName]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("attribute %s not supported", attributeName)
|
||||
}
|
||||
return v.GetField()
|
||||
}
|
||||
|
||||
func searchFilterForEquality(filter message.Filter, desc string, values ...string) string {
|
||||
switch f := filter.(type) {
|
||||
case message.FilterAnd:
|
||||
for _, child := range f {
|
||||
if val := searchFilterForEquality(child, desc, values...); val != "" {
|
||||
return val
|
||||
}
|
||||
}
|
||||
case message.FilterOr:
|
||||
for _, child := range f {
|
||||
if val := searchFilterForEquality(child, desc, values...); val != "" {
|
||||
return val
|
||||
}
|
||||
}
|
||||
case message.FilterNot:
|
||||
return searchFilterForEquality(f.Filter, desc, values...)
|
||||
case message.FilterSubstrings:
|
||||
// Handle FilterSubstrings case if needed
|
||||
case message.FilterEqualityMatch:
|
||||
if strings.EqualFold(string(f.AttributeDesc()), desc) {
|
||||
for _, value := range values {
|
||||
if val := string(f.AssertionValue()); val == value {
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
case message.FilterGreaterOrEqual:
|
||||
// Handle FilterGreaterOrEqual case if needed
|
||||
case message.FilterLessOrEqual:
|
||||
// Handle FilterLessOrEqual case if needed
|
||||
case message.FilterPresent:
|
||||
// Handle FilterPresent case if needed
|
||||
case message.FilterApproxMatch:
|
||||
// Handle FilterApproxMatch case if needed
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
2
main.go
2
main.go
@ -36,12 +36,12 @@ func main() {
|
||||
object.CreateTables()
|
||||
|
||||
object.InitDb()
|
||||
object.InitFromFile()
|
||||
object.InitDefaultStorageProvider()
|
||||
object.InitLdapAutoSynchronizer()
|
||||
proxy.InitHttpClient()
|
||||
authz.InitApi()
|
||||
object.InitUserManager()
|
||||
object.InitFromFile()
|
||||
object.InitCasvisorConfig()
|
||||
|
||||
util.SafeGoroutine(func() { object.RunSyncUsersJob() })
|
||||
|
@ -37,7 +37,7 @@ type Adapter struct {
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
Password string `xorm:"varchar(150)" json:"password"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
|
||||
*xormadapter.Adapter `xorm:"-" json:"-"`
|
||||
@ -178,6 +178,7 @@ func (adapter *Adapter) InitAdapter() error {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
||||
}
|
||||
|
||||
dataSourceName = conf.ReplaceDataSourceNameByDocker(dataSourceName)
|
||||
engine, err := xorm.NewEngine(driverName, dataSourceName)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
@ -37,12 +36,22 @@ type SignupItem struct {
|
||||
Prompted bool `json:"prompted"`
|
||||
Label string `json:"label"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
Regex string `json:"regex"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
|
||||
type SigninItem struct {
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Label string `json:"label"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
Rule string `json:"rule"`
|
||||
IsCustom bool `json:"isCustom"`
|
||||
}
|
||||
|
||||
type SamlItem struct {
|
||||
Name string `json:"name"`
|
||||
NameFormat string `json:"nameformat"`
|
||||
NameFormat string `json:"nameFormat"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
@ -51,32 +60,33 @@ type Application struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Logo string `xorm:"varchar(200)" json:"logo"`
|
||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
EnablePassword bool `json:"enablePassword"`
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableSigninSession bool `json:"enableSigninSession"`
|
||||
EnableAutoSignin bool `json:"enableAutoSignin"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
|
||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Logo string `xorm:"varchar(200)" json:"logo"`
|
||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
EnablePassword bool `json:"enablePassword"`
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableSigninSession bool `json:"enableSigninSession"`
|
||||
EnableAutoSignin bool `json:"enableAutoSignin"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
||||
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
|
||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
@ -100,7 +110,7 @@ type Application struct {
|
||||
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
|
||||
|
||||
FailedSigninLimit int `json:"failedSigninLimit"`
|
||||
FailedSigninfrozenTime int `json:"failedSigninfrozenTime"`
|
||||
FailedSigninFrozenTime int `json:"failedSigninFrozenTime"`
|
||||
}
|
||||
|
||||
func GetApplicationCount(owner, field, value string) (int64, error) {
|
||||
@ -163,15 +173,6 @@ func getProviderMap(owner string) (m map[string]*Provider, err error) {
|
||||
|
||||
m = map[string]*Provider{}
|
||||
for _, provider := range providers {
|
||||
// Get QRCode only once
|
||||
if provider.Type == "WeChat" && provider.DisableSsl && provider.Content == "" {
|
||||
provider.Content, err = idp.GetWechatOfficialAccountQRCode(provider.ClientId2, provider.ClientSecret2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
UpdateProvider(provider.Owner+"/"+provider.Name, provider)
|
||||
}
|
||||
|
||||
m[provider.Name] = GetMaskedProvider(provider, true)
|
||||
}
|
||||
|
||||
@ -199,6 +200,100 @@ func extendApplicationWithOrg(application *Application) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func extendApplicationWithSigninItems(application *Application) (err error) {
|
||||
if len(application.SigninItems) == 0 {
|
||||
signinItem := &SigninItem{
|
||||
Name: "Back button",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .back-button {\n top: 65px;\n left: 15px;\n position: absolute;\n }\n</style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Languages",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .login-languages {\n top: 55px;\n right: 5px;\n position: absolute;\n }\n</style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Logo",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .login-logo-box {\n }\n<style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Signin methods",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .signin-methods {\n }\n<style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Username",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .login-username {\n }\n<style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Password",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .login-password {\n }\n<style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Agreement",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .login-agreement {\n }\n<style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Forgot password?",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .login-forget-password {\n display: inline-flex;\n justify-content: space-between;\n width: 320px;\n margin-bottom: 25px;\n }\n<style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Login button",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .login-button-box {\n margin-bottom: 5px;\n }\n .login-button {\n width: 100%;\n }\n<style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Signup link",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .login-signup-link {\n margin-bottom: 24px;\n display: flex;\n justify-content: end;\n}\n<style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
signinItem = &SigninItem{
|
||||
Name: "Providers",
|
||||
Visible: true,
|
||||
Label: "\n<style>\n .provider-img {\n width: 30px;\n margin: 5px;\n }\n .provider-big-img {\n margin-bottom: 10px;\n }\n</style>\n",
|
||||
Placeholder: "",
|
||||
Rule: "None",
|
||||
}
|
||||
application.SigninItems = append(application.SigninItems, signinItem)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func extendApplicationWithSigninMethods(application *Application) (err error) {
|
||||
if len(application.SigninMethods) == 0 {
|
||||
if application.EnablePassword {
|
||||
@ -249,6 +344,10 @@ func getApplication(owner string, name string) (*Application, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = extendApplicationWithSigninItems(&application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &application, nil
|
||||
} else {
|
||||
@ -279,6 +378,11 @@ func GetApplicationByOrganizationName(organization string) (*Application, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = extendApplicationWithSigninItems(&application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &application, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
@ -331,6 +435,11 @@ func GetApplicationByClientId(clientId string) (*Application, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = extendApplicationWithSigninItems(&application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &application, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
@ -347,6 +456,17 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
||||
return nil
|
||||
}
|
||||
|
||||
if application.TokenFields == nil {
|
||||
application.TokenFields = []string{}
|
||||
}
|
||||
|
||||
if application.FailedSigninLimit == 0 {
|
||||
application.FailedSigninLimit = DefaultFailedSigninLimit
|
||||
}
|
||||
if application.FailedSigninFrozenTime == 0 {
|
||||
application.FailedSigninFrozenTime = DefaultFailedSigninFrozenTime
|
||||
}
|
||||
|
||||
if userId != "" {
|
||||
if isUserIdGlobalAdmin(userId) {
|
||||
return application
|
||||
@ -380,10 +500,6 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
||||
}
|
||||
}
|
||||
|
||||
if application.InvitationCodes != nil {
|
||||
application.InvitationCodes = []string{"***"}
|
||||
}
|
||||
|
||||
return application
|
||||
}
|
||||
|
||||
|
BIN
object/cert.go~
BIN
object/cert.go~
Binary file not shown.
135
object/check.go
135
object/check.go
@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
@ -28,94 +29,93 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultFailedSigninLimit = 5
|
||||
// DefaultFailedSigninfrozenTime The unit of frozen time is minutes
|
||||
DefaultFailedSigninfrozenTime = 15
|
||||
DefaultFailedSigninLimit = 5
|
||||
DefaultFailedSigninFrozenTime = 15
|
||||
)
|
||||
|
||||
func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string {
|
||||
func CheckUserSignup(application *Application, organization *Organization, authForm *form.AuthForm, lang string) string {
|
||||
if organization == nil {
|
||||
return i18n.Translate(lang, "check:Organization does not exist")
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Username") {
|
||||
if len(form.Username) <= 1 {
|
||||
if len(authForm.Username) <= 1 {
|
||||
return i18n.Translate(lang, "check:Username must have at least 2 characters")
|
||||
}
|
||||
if unicode.IsDigit(rune(form.Username[0])) {
|
||||
if unicode.IsDigit(rune(authForm.Username[0])) {
|
||||
return i18n.Translate(lang, "check:Username cannot start with a digit")
|
||||
}
|
||||
if util.IsEmailValid(form.Username) {
|
||||
if util.IsEmailValid(authForm.Username) {
|
||||
return i18n.Translate(lang, "check:Username cannot be an email address")
|
||||
}
|
||||
if util.ReWhiteSpace.MatchString(form.Username) {
|
||||
if util.ReWhiteSpace.MatchString(authForm.Username) {
|
||||
return i18n.Translate(lang, "check:Username cannot contain white spaces")
|
||||
}
|
||||
|
||||
if msg := CheckUsername(form.Username, lang); msg != "" {
|
||||
if msg := CheckUsername(authForm.Username, lang); msg != "" {
|
||||
return msg
|
||||
}
|
||||
|
||||
if HasUserByField(organization.Name, "name", form.Username) {
|
||||
if HasUserByField(organization.Name, "name", authForm.Username) {
|
||||
return i18n.Translate(lang, "check:Username already exists")
|
||||
}
|
||||
if HasUserByField(organization.Name, "email", form.Email) {
|
||||
if HasUserByField(organization.Name, "email", authForm.Email) {
|
||||
return i18n.Translate(lang, "check:Email already exists")
|
||||
}
|
||||
if HasUserByField(organization.Name, "phone", form.Phone) {
|
||||
if HasUserByField(organization.Name, "phone", authForm.Phone) {
|
||||
return i18n.Translate(lang, "check:Phone already exists")
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Password") {
|
||||
msg := CheckPasswordComplexityByOrg(organization, form.Password)
|
||||
msg := CheckPasswordComplexityByOrg(organization, authForm.Password)
|
||||
if msg != "" {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") {
|
||||
if form.Email == "" {
|
||||
if authForm.Email == "" {
|
||||
if application.IsSignupItemRequired("Email") {
|
||||
return i18n.Translate(lang, "check:Email cannot be empty")
|
||||
}
|
||||
} else {
|
||||
if HasUserByField(organization.Name, "email", form.Email) {
|
||||
if HasUserByField(organization.Name, "email", authForm.Email) {
|
||||
return i18n.Translate(lang, "check:Email already exists")
|
||||
} else if !util.IsEmailValid(form.Email) {
|
||||
} else if !util.IsEmailValid(authForm.Email) {
|
||||
return i18n.Translate(lang, "check:Email is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Phone") {
|
||||
if form.Phone == "" {
|
||||
if authForm.Phone == "" {
|
||||
if application.IsSignupItemRequired("Phone") {
|
||||
return i18n.Translate(lang, "check:Phone cannot be empty")
|
||||
}
|
||||
} else {
|
||||
if HasUserByField(organization.Name, "phone", form.Phone) {
|
||||
if HasUserByField(organization.Name, "phone", authForm.Phone) {
|
||||
return i18n.Translate(lang, "check:Phone already exists")
|
||||
} else if !util.IsPhoneAllowInRegin(form.CountryCode, organization.CountryCodes) {
|
||||
} else if !util.IsPhoneAllowInRegin(authForm.CountryCode, organization.CountryCodes) {
|
||||
return i18n.Translate(lang, "check:Your region is not allow to signup by phone")
|
||||
} else if !util.IsPhoneValid(form.Phone, form.CountryCode) {
|
||||
} else if !util.IsPhoneValid(authForm.Phone, authForm.CountryCode) {
|
||||
return i18n.Translate(lang, "check:Phone number is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Display name") {
|
||||
if application.GetSignupItemRule("Display name") == "First, last" && (form.FirstName != "" || form.LastName != "") {
|
||||
if form.FirstName == "" {
|
||||
if application.GetSignupItemRule("Display name") == "First, last" && (authForm.FirstName != "" || authForm.LastName != "") {
|
||||
if authForm.FirstName == "" {
|
||||
return i18n.Translate(lang, "check:FirstName cannot be blank")
|
||||
} else if form.LastName == "" {
|
||||
} else if authForm.LastName == "" {
|
||||
return i18n.Translate(lang, "check:LastName cannot be blank")
|
||||
}
|
||||
} else {
|
||||
if form.Name == "" {
|
||||
if authForm.Name == "" {
|
||||
return i18n.Translate(lang, "check:DisplayName cannot be blank")
|
||||
} else if application.GetSignupItemRule("Display name") == "Real name" {
|
||||
if !isValidRealName(form.Name) {
|
||||
if !isValidRealName(authForm.Name) {
|
||||
return i18n.Translate(lang, "check:DisplayName is not valid real name")
|
||||
}
|
||||
}
|
||||
@ -123,28 +123,78 @@ func CheckUserSignup(application *Application, organization *Organization, form
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Affiliation") {
|
||||
if form.Affiliation == "" {
|
||||
if authForm.Affiliation == "" {
|
||||
return i18n.Translate(lang, "check:Affiliation cannot be blank")
|
||||
}
|
||||
}
|
||||
|
||||
if len(application.InvitationCodes) > 0 {
|
||||
if form.InvitationCode == "" {
|
||||
if application.IsSignupItemRequired("Invitation code") {
|
||||
return i18n.Translate(lang, "check:Invitation code cannot be blank")
|
||||
}
|
||||
} else {
|
||||
if !util.InSlice(application.InvitationCodes, form.InvitationCode) {
|
||||
return i18n.Translate(lang, "check:Invitation code is invalid")
|
||||
}
|
||||
for _, signupItem := range application.SignupItems {
|
||||
if signupItem.Regex == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
isString, value := form.GetAuthFormFieldValue(authForm, signupItem.Name)
|
||||
if !isString {
|
||||
continue
|
||||
}
|
||||
|
||||
regexSignupItem, err := regexp.Compile(signupItem.Regex)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
matched := regexSignupItem.MatchString(value)
|
||||
if !matched {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:The value \"%s\" for signup field \"%s\" doesn't match the signup item regex of the application \"%s\""), value, signupItem.Name, application.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckInvitationCode(application *Application, organization *Organization, authForm *form.AuthForm, lang string) (*Invitation, string) {
|
||||
if authForm.InvitationCode == "" {
|
||||
if application.IsSignupItemRequired("Invitation code") {
|
||||
return nil, i18n.Translate(lang, "check:Invitation code cannot be blank")
|
||||
} else {
|
||||
return nil, ""
|
||||
}
|
||||
}
|
||||
|
||||
invitations, err := GetInvitations(organization.Name)
|
||||
if err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
errMsg := ""
|
||||
for _, invitation := range invitations {
|
||||
if invitation.Application != application.Name && invitation.Application != "All" {
|
||||
continue
|
||||
}
|
||||
if isValid, msg := invitation.IsInvitationCodeValid(application, authForm.InvitationCode, authForm.Username, authForm.Email, authForm.Phone, lang); isValid {
|
||||
return invitation, msg
|
||||
} else if msg != "" && errMsg == "" {
|
||||
errMsg = msg
|
||||
}
|
||||
}
|
||||
|
||||
if errMsg != "" {
|
||||
return nil, errMsg
|
||||
} else {
|
||||
return nil, i18n.Translate(lang, "check:Invitation code is invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func CheckInvitationDefaultCode(code string, defaultCode string, lang string) error {
|
||||
if matched, err := util.IsInvitationCodeMatch(code, defaultCode); err != nil {
|
||||
return err
|
||||
} else if !matched {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Default code does not match the code's matching rules"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkSigninErrorTimes(user *User, lang string) error {
|
||||
failedSigninLimit, failedSigninfrozenTime, err := GetFailedSigninConfigByUser(user)
|
||||
failedSigninLimit, failedSigninFrozenTime, err := GetFailedSigninConfigByUser(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -152,7 +202,7 @@ func checkSigninErrorTimes(user *User, lang string) error {
|
||||
if user.SigninWrongTimes >= failedSigninLimit {
|
||||
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
|
||||
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
|
||||
minutes := failedSigninfrozenTime - int(passedTime.Minutes())
|
||||
minutes := failedSigninFrozenTime - int(passedTime.Minutes())
|
||||
|
||||
// deny the login if the error times is greater than the limit and the last login time is less than the duration
|
||||
if minutes > 0 {
|
||||
@ -273,7 +323,7 @@ func checkLdapUserPassword(user *User, password string, lang string) error {
|
||||
}
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:LDAP user name or password incorrect"))
|
||||
}
|
||||
return nil
|
||||
return resetUserSigninErrorTimes(user)
|
||||
}
|
||||
|
||||
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, error) {
|
||||
@ -509,12 +559,11 @@ func CheckToEnableCaptcha(application *Application, organization, username strin
|
||||
return false, err
|
||||
}
|
||||
|
||||
var failedSigninLimit int
|
||||
if application.FailedSigninLimit == 0 {
|
||||
failedSigninLimit = 5
|
||||
} else {
|
||||
failedSigninLimit = application.FailedSigninLimit
|
||||
failedSigninLimit := application.FailedSigninLimit
|
||||
if failedSigninLimit == 0 {
|
||||
failedSigninLimit = DefaultFailedSigninLimit
|
||||
}
|
||||
|
||||
return user != nil && user.SigninWrongTimes >= failedSigninLimit, nil
|
||||
}
|
||||
return providerItem.Rule == "Always", nil
|
||||
|
@ -24,7 +24,7 @@ var (
|
||||
regexLowerCase = regexp.MustCompile(`[a-z]`)
|
||||
regexUpperCase = regexp.MustCompile(`[A-Z]`)
|
||||
regexDigit = regexp.MustCompile(`\d`)
|
||||
regexSpecial = regexp.MustCompile(`[!@#$%^&*]`)
|
||||
regexSpecial = regexp.MustCompile("[!-/:-@[-`{-~]")
|
||||
)
|
||||
|
||||
func isValidOption_AtLeast6(password string) string {
|
||||
|
@ -52,18 +52,18 @@ func GetFailedSigninConfigByUser(user *User) (int, int, error) {
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
failedSigninLimit := application.FailedSigninLimit
|
||||
failedSigninfrozenTime := application.FailedSigninfrozenTime
|
||||
|
||||
// 0 as an initialization value, corresponding to the default configuration parameters
|
||||
failedSigninLimit := application.FailedSigninLimit
|
||||
if failedSigninLimit == 0 {
|
||||
failedSigninLimit = DefaultFailedSigninLimit
|
||||
}
|
||||
if failedSigninfrozenTime == 0 {
|
||||
failedSigninfrozenTime = DefaultFailedSigninfrozenTime
|
||||
|
||||
failedSigninFrozenTime := application.FailedSigninFrozenTime
|
||||
if failedSigninFrozenTime == 0 {
|
||||
failedSigninFrozenTime = DefaultFailedSigninFrozenTime
|
||||
}
|
||||
|
||||
return failedSigninLimit, failedSigninfrozenTime, nil
|
||||
return failedSigninLimit, failedSigninFrozenTime, nil
|
||||
}
|
||||
|
||||
func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
|
||||
@ -72,7 +72,7 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
|
||||
failedSigninLimit, failedSigninfrozenTime, errSignin := GetFailedSigninConfigByUser(user)
|
||||
failedSigninLimit, failedSigninFrozenTime, errSignin := GetFailedSigninConfigByUser(user)
|
||||
if errSignin != nil {
|
||||
return errSignin
|
||||
}
|
||||
@ -101,5 +101,5 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
|
||||
}
|
||||
|
||||
// don't show the chance error message if the user has no chance left
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), failedSigninfrozenTime)
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), failedSigninFrozenTime)
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ func initBuiltInApplication() {
|
||||
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, SignupGroup: "", Rule: "None", Provider: nil},
|
||||
},
|
||||
SigninMethods: []*SigninMethod{
|
||||
{Name: "Password", DisplayName: "Password", Rule: "None"},
|
||||
{Name: "Password", DisplayName: "Password", Rule: "All"},
|
||||
{Name: "Verification code", DisplayName: "Verification code", Rule: "All"},
|
||||
{Name: "WebAuthn", DisplayName: "WebAuthn", Rule: "None"},
|
||||
},
|
||||
|
@ -35,6 +35,11 @@ type InitData struct {
|
||||
Syncers []*Syncer `json:"syncers"`
|
||||
Tokens []*Token `json:"tokens"`
|
||||
Webhooks []*Webhook `json:"webhooks"`
|
||||
Groups []*Group `json:"groups"`
|
||||
Adapters []*Adapter `json:"adapters"`
|
||||
Enforcers []*Enforcer `json:"enforcers"`
|
||||
Plans []*Plan `json:"plans"`
|
||||
Pricings []*Pricing `json:"pricings"`
|
||||
}
|
||||
|
||||
func InitFromFile() {
|
||||
@ -94,6 +99,21 @@ func InitFromFile() {
|
||||
for _, webhook := range initData.Webhooks {
|
||||
initDefinedWebhook(webhook)
|
||||
}
|
||||
for _, group := range initData.Groups {
|
||||
initDefinedGroup(group)
|
||||
}
|
||||
for _, adapter := range initData.Adapters {
|
||||
initDefinedAdapter(adapter)
|
||||
}
|
||||
for _, enforcer := range initData.Enforcers {
|
||||
initDefinedEnforcer(enforcer)
|
||||
}
|
||||
for _, plan := range initData.Plans {
|
||||
initDefinedPlan(plan)
|
||||
}
|
||||
for _, pricing := range initData.Pricings {
|
||||
initDefinedPricing(pricing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,6 +140,11 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
|
||||
Syncers: []*Syncer{},
|
||||
Tokens: []*Token{},
|
||||
Webhooks: []*Webhook{},
|
||||
Groups: []*Group{},
|
||||
Adapters: []*Adapter{},
|
||||
Enforcers: []*Enforcer{},
|
||||
Plans: []*Plan{},
|
||||
Pricings: []*Pricing{},
|
||||
}
|
||||
err := util.JsonToStruct(s, data)
|
||||
if err != nil {
|
||||
@ -190,7 +215,16 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
|
||||
webhook.Headers = []*Header{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, plan := range data.Plans {
|
||||
if plan.PaymentProviders == nil {
|
||||
plan.PaymentProviders = []string{}
|
||||
}
|
||||
}
|
||||
for _, pricing := range data.Pricings {
|
||||
if pricing.Plans == nil {
|
||||
pricing.Plans = []string{}
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@ -434,3 +468,78 @@ func initDefinedWebhook(webhook *Webhook) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initDefinedGroup(group *Group) {
|
||||
existed, err := getGroup(group.Owner, group.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
group.CreatedTime = util.GetCurrentTime()
|
||||
_, err = AddGroup(group)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initDefinedAdapter(adapter *Adapter) {
|
||||
existed, err := getAdapter(adapter.Owner, adapter.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
adapter.CreatedTime = util.GetCurrentTime()
|
||||
_, err = AddAdapter(adapter)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initDefinedEnforcer(enforcer *Enforcer) {
|
||||
existed, err := getEnforcer(enforcer.Owner, enforcer.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
enforcer.CreatedTime = util.GetCurrentTime()
|
||||
_, err = AddEnforcer(enforcer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initDefinedPlan(plan *Plan) {
|
||||
existed, err := getPlan(plan.Owner, plan.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
plan.CreatedTime = util.GetCurrentTime()
|
||||
_, err = AddPlan(plan)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initDefinedPricing(pricing *Pricing) {
|
||||
existed, err := getPlan(pricing.Owner, pricing.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
pricing.CreatedTime = util.GetCurrentTime()
|
||||
_, err = AddPricing(pricing)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -96,6 +96,31 @@ func writeInitDataToFile(filePath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
groups, err := GetGroups("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
adapters, err := GetAdapters("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enforcers, err := GetEnforcers("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
plans, err := GetPlans("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pricings, err := GetPricings("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := &InitData{
|
||||
Organizations: organizations,
|
||||
Applications: applications,
|
||||
@ -112,6 +137,11 @@ func writeInitDataToFile(filePath string) error {
|
||||
Syncers: syncers,
|
||||
Tokens: tokens,
|
||||
Webhooks: webhooks,
|
||||
Groups: groups,
|
||||
Adapters: adapters,
|
||||
Enforcers: enforcers,
|
||||
Plans: plans,
|
||||
Pricings: pricings,
|
||||
}
|
||||
|
||||
text := util.StructToJsonFormatted(data)
|
||||
|
@ -17,6 +17,7 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
@ -28,7 +29,8 @@ type Invitation struct {
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
Code string `xorm:"varchar(100)" json:"code"`
|
||||
Code string `xorm:"varchar(100) index" json:"code"`
|
||||
IsRegexp bool `json:"isRegexp"`
|
||||
Quota int `json:"quota"`
|
||||
UsedCount int `json:"usedCount"`
|
||||
|
||||
@ -38,6 +40,7 @@ type Invitation struct {
|
||||
Phone string `xorm:"varchar(100)" json:"phone"`
|
||||
|
||||
SignupGroup string `xorm:"varchar(100)" json:"signupGroup"`
|
||||
DefaultCode string `xorm:"varchar(100)" json:"defaultCode"`
|
||||
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
}
|
||||
@ -91,7 +94,45 @@ func GetInvitation(id string) (*Invitation, error) {
|
||||
return getInvitation(owner, name)
|
||||
}
|
||||
|
||||
func UpdateInvitation(id string, invitation *Invitation) (bool, error) {
|
||||
func GetInvitationByCode(code string, organizationName string, lang string) (*Invitation, string) {
|
||||
invitations, err := GetInvitations(organizationName)
|
||||
if err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
errMsg := ""
|
||||
for _, invitation := range invitations {
|
||||
if isValid, msg := invitation.SimpleCheckInvitationCode(code, lang); isValid {
|
||||
return invitation, msg
|
||||
} else if msg != "" && errMsg == "" {
|
||||
errMsg = msg
|
||||
}
|
||||
}
|
||||
|
||||
if errMsg != "" {
|
||||
return nil, errMsg
|
||||
} else {
|
||||
return nil, i18n.Translate(lang, "check:Invitation code is invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func GetMaskedInvitation(invitation *Invitation) *Invitation {
|
||||
if invitation == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
invitation.CreatedTime = ""
|
||||
invitation.UpdatedTime = ""
|
||||
invitation.Code = "***"
|
||||
invitation.DefaultCode = "***"
|
||||
invitation.IsRegexp = false
|
||||
invitation.Quota = -1
|
||||
invitation.UsedCount = -1
|
||||
invitation.SignupGroup = ""
|
||||
|
||||
return invitation
|
||||
}
|
||||
|
||||
func UpdateInvitation(id string, invitation *Invitation, lang string) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if p, err := getInvitation(owner, name); err != nil {
|
||||
return false, err
|
||||
@ -99,6 +140,17 @@ func UpdateInvitation(id string, invitation *Invitation) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if isRegexp, err := util.IsRegexp(invitation.Code); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
invitation.IsRegexp = isRegexp
|
||||
}
|
||||
|
||||
err := CheckInvitationDefaultCode(invitation.Code, invitation.DefaultCode, lang)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(invitation)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -107,7 +159,18 @@ func UpdateInvitation(id string, invitation *Invitation) (bool, error) {
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func AddInvitation(invitation *Invitation) (bool, error) {
|
||||
func AddInvitation(invitation *Invitation, lang string) (bool, error) {
|
||||
if isRegexp, err := util.IsRegexp(invitation.Code); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
invitation.IsRegexp = isRegexp
|
||||
}
|
||||
|
||||
err := CheckInvitationDefaultCode(invitation.Code, invitation.DefaultCode, lang)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.Insert(invitation)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -132,3 +195,43 @@ func (invitation *Invitation) GetId() string {
|
||||
func VerifyInvitation(id string) (payment *Payment, attachInfo map[string]interface{}, err error) {
|
||||
return nil, nil, fmt.Errorf("the invitation: %s does not exist", id)
|
||||
}
|
||||
|
||||
func (invitation *Invitation) SimpleCheckInvitationCode(invitationCode string, lang string) (bool, string) {
|
||||
if matched, err := util.IsInvitationCodeMatch(invitation.Code, invitationCode); err != nil {
|
||||
return false, err.Error()
|
||||
} else if !matched {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if invitation.State != "Active" {
|
||||
return false, i18n.Translate(lang, "check:Invitation code suspended")
|
||||
}
|
||||
if invitation.UsedCount >= invitation.Quota {
|
||||
return false, i18n.Translate(lang, "check:Invitation code exhausted")
|
||||
}
|
||||
|
||||
// Determine whether the invitation code is in the form of a regular expression other than pure numbers and letters
|
||||
if invitation.IsRegexp {
|
||||
user, _ := GetUserByInvitationCode(invitation.Owner, invitationCode)
|
||||
if user != nil {
|
||||
return false, i18n.Translate(lang, "check:The invitation code has already been used")
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (invitation *Invitation) IsInvitationCodeValid(application *Application, invitationCode string, username string, email string, phone string, lang string) (bool, string) {
|
||||
if isValid, msg := invitation.SimpleCheckInvitationCode(invitationCode, lang); !isValid {
|
||||
return false, msg
|
||||
}
|
||||
if application.IsSignupItemRequired("Username") && invitation.Username != "" && invitation.Username != username {
|
||||
return false, i18n.Translate(lang, "check:Please register using the username corresponding to the invitation code")
|
||||
}
|
||||
if application.IsSignupItemRequired("Email") && invitation.Email != "" && invitation.Email != email {
|
||||
return false, i18n.Translate(lang, "check:Please register using the email corresponding to the invitation code")
|
||||
}
|
||||
if application.IsSignupItemRequired("Phone") && invitation.Phone != "" && invitation.Phone != phone {
|
||||
return false, i18n.Translate(lang, "check:Please register using the phone corresponding to the invitation code")
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
@ -18,12 +18,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
)
|
||||
|
||||
const MfaRecoveryCodesSession = "mfa_recovery_codes"
|
||||
|
||||
type MfaProps struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
IsPreferred bool `json:"isPreferred"`
|
||||
@ -35,9 +31,9 @@ type MfaProps struct {
|
||||
}
|
||||
|
||||
type MfaInterface interface {
|
||||
Initiate(ctx *context.Context, userId string) (*MfaProps, error)
|
||||
SetupVerify(ctx *context.Context, passcode string) error
|
||||
Enable(ctx *context.Context, user *User) error
|
||||
Initiate(userId string) (*MfaProps, error)
|
||||
SetupVerify(passcode string) error
|
||||
Enable(user *User) error
|
||||
Verify(passcode string) error
|
||||
}
|
||||
|
||||
|
@ -16,90 +16,46 @@ package object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
MfaCountryCodeSession = "mfa_country_code"
|
||||
MfaDestSession = "mfa_dest"
|
||||
)
|
||||
|
||||
type SmsMfa struct {
|
||||
Config *MfaProps
|
||||
*MfaProps
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
|
||||
recoveryCode := uuid.NewString()
|
||||
|
||||
err := ctx.Input.CruSession.Set(MfaRecoveryCodesSession, []string{recoveryCode})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) Initiate(userId string) (*MfaProps, error) {
|
||||
mfaProps := MfaProps{
|
||||
MfaType: mfa.Config.MfaType,
|
||||
RecoveryCodes: []string{recoveryCode},
|
||||
MfaType: mfa.MfaType,
|
||||
}
|
||||
return &mfaProps, nil
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) SetupVerify(ctx *context.Context, passCode string) error {
|
||||
destSession := ctx.Input.CruSession.Get(MfaDestSession)
|
||||
if destSession == nil {
|
||||
return errors.New("dest session is missing")
|
||||
}
|
||||
dest := destSession.(string)
|
||||
|
||||
if !util.IsEmailValid(dest) {
|
||||
countryCodeSession := ctx.Input.CruSession.Get(MfaCountryCodeSession)
|
||||
if countryCodeSession == nil {
|
||||
return errors.New("country code is missing")
|
||||
}
|
||||
countryCode := countryCodeSession.(string)
|
||||
|
||||
dest, _ = util.GetE164Number(dest, countryCode)
|
||||
func (mfa *SmsMfa) SetupVerify(passCode string) error {
|
||||
if !util.IsEmailValid(mfa.Secret) {
|
||||
mfa.Secret, _ = util.GetE164Number(mfa.Secret, mfa.CountryCode)
|
||||
}
|
||||
|
||||
if result := CheckVerificationCode(dest, passCode, "en"); result.Code != VerificationSuccess {
|
||||
if result := CheckVerificationCode(mfa.Secret, passCode, "en"); result.Code != VerificationSuccess {
|
||||
return errors.New(result.Msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
|
||||
recoveryCodes := ctx.Input.CruSession.Get(MfaRecoveryCodesSession).([]string)
|
||||
if len(recoveryCodes) == 0 {
|
||||
return fmt.Errorf("recovery codes is missing")
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) Enable(user *User) error {
|
||||
columns := []string{"recovery_codes", "preferred_mfa_type"}
|
||||
|
||||
user.RecoveryCodes = append(user.RecoveryCodes, recoveryCodes...)
|
||||
user.RecoveryCodes = append(user.RecoveryCodes, mfa.RecoveryCodes...)
|
||||
if user.PreferredMfaType == "" {
|
||||
user.PreferredMfaType = mfa.Config.MfaType
|
||||
user.PreferredMfaType = mfa.MfaType
|
||||
}
|
||||
|
||||
if mfa.Config.MfaType == SmsType {
|
||||
if mfa.MfaType == SmsType {
|
||||
user.MfaPhoneEnabled = true
|
||||
columns = append(columns, "mfa_phone_enabled")
|
||||
|
||||
if user.Phone == "" {
|
||||
user.Phone = ctx.Input.CruSession.Get(MfaDestSession).(string)
|
||||
user.CountryCode = ctx.Input.CruSession.Get(MfaCountryCodeSession).(string)
|
||||
columns = append(columns, "phone", "country_code")
|
||||
}
|
||||
} else if mfa.Config.MfaType == EmailType {
|
||||
columns = append(columns, "mfa_phone_enabled", "phone", "country_code")
|
||||
} else if mfa.MfaType == EmailType {
|
||||
user.MfaEmailEnabled = true
|
||||
columns = append(columns, "mfa_email_enabled")
|
||||
|
||||
if user.Email == "" {
|
||||
user.Email = ctx.Input.CruSession.Get(MfaDestSession).(string)
|
||||
columns = append(columns, "email")
|
||||
}
|
||||
columns = append(columns, "mfa_email_enabled", "email")
|
||||
}
|
||||
|
||||
_, err := UpdateUser(user.GetId(), user, columns, false)
|
||||
@ -107,18 +63,14 @@ func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Input.CruSession.Delete(MfaRecoveryCodesSession)
|
||||
ctx.Input.CruSession.Delete(MfaDestSession)
|
||||
ctx.Input.CruSession.Delete(MfaCountryCodeSession)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) Verify(passCode string) error {
|
||||
if !util.IsEmailValid(mfa.Config.Secret) {
|
||||
mfa.Config.Secret, _ = util.GetE164Number(mfa.Config.Secret, mfa.Config.CountryCode)
|
||||
if !util.IsEmailValid(mfa.Secret) {
|
||||
mfa.Secret, _ = util.GetE164Number(mfa.Secret, mfa.CountryCode)
|
||||
}
|
||||
if result := CheckVerificationCode(mfa.Config.Secret, passCode, "en"); result.Code != VerificationSuccess {
|
||||
if result := CheckVerificationCode(mfa.Secret, passCode, "en"); result.Code != VerificationSuccess {
|
||||
return errors.New(result.Msg)
|
||||
}
|
||||
return nil
|
||||
@ -131,7 +83,7 @@ func NewSmsMfaUtil(config *MfaProps) *SmsMfa {
|
||||
}
|
||||
}
|
||||
return &SmsMfa{
|
||||
Config: config,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,6 +94,6 @@ func NewEmailMfaUtil(config *MfaProps) *SmsMfa {
|
||||
}
|
||||
}
|
||||
return &SmsMfa{
|
||||
Config: config,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
@ -16,28 +16,24 @@ package object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
const (
|
||||
MfaTotpSecretSession = "mfa_totp_secret"
|
||||
MfaTotpPeriodInSeconds = 30
|
||||
)
|
||||
|
||||
type TotpMfa struct {
|
||||
Config *MfaProps
|
||||
*MfaProps
|
||||
period uint
|
||||
secretSize uint
|
||||
digits otp.Digits
|
||||
}
|
||||
|
||||
func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
|
||||
func (mfa *TotpMfa) Initiate(userId string) (*MfaProps, error) {
|
||||
//issuer := beego.AppConfig.String("appname")
|
||||
//if issuer == "" {
|
||||
// issuer = "casdoor"
|
||||
@ -55,33 +51,16 @@ func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, er
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ctx.Input.CruSession.Set(MfaTotpSecretSession, key.Secret())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recoveryCode := uuid.NewString()
|
||||
err = ctx.Input.CruSession.Set(MfaRecoveryCodesSession, []string{recoveryCode})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mfaProps := MfaProps{
|
||||
MfaType: mfa.Config.MfaType,
|
||||
RecoveryCodes: []string{recoveryCode},
|
||||
Secret: key.Secret(),
|
||||
URL: key.URL(),
|
||||
MfaType: mfa.MfaType,
|
||||
Secret: key.Secret(),
|
||||
URL: key.URL(),
|
||||
}
|
||||
return &mfaProps, nil
|
||||
}
|
||||
|
||||
func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
|
||||
secret := ctx.Input.CruSession.Get(MfaTotpSecretSession)
|
||||
if secret == nil {
|
||||
return errors.New("totp secret is missing")
|
||||
}
|
||||
|
||||
result, err := totp.ValidateCustom(passcode, secret.(string), time.Now().UTC(), totp.ValidateOpts{
|
||||
func (mfa *TotpMfa) SetupVerify(passcode string) error {
|
||||
result, err := totp.ValidateCustom(passcode, mfa.Secret, time.Now().UTC(), totp.ValidateOpts{
|
||||
Period: MfaTotpPeriodInSeconds,
|
||||
Skew: 1,
|
||||
Digits: otp.DigitsSix,
|
||||
@ -98,22 +77,13 @@ func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (mfa *TotpMfa) Enable(ctx *context.Context, user *User) error {
|
||||
recoveryCodes := ctx.Input.CruSession.Get(MfaRecoveryCodesSession).([]string)
|
||||
if len(recoveryCodes) == 0 {
|
||||
return fmt.Errorf("recovery codes is missing")
|
||||
}
|
||||
secret := ctx.Input.CruSession.Get(MfaTotpSecretSession).(string)
|
||||
if secret == "" {
|
||||
return fmt.Errorf("totp secret is missing")
|
||||
}
|
||||
|
||||
func (mfa *TotpMfa) Enable(user *User) error {
|
||||
columns := []string{"recovery_codes", "preferred_mfa_type", "totp_secret"}
|
||||
|
||||
user.RecoveryCodes = append(user.RecoveryCodes, recoveryCodes...)
|
||||
user.TotpSecret = secret
|
||||
user.RecoveryCodes = append(user.RecoveryCodes, mfa.RecoveryCodes...)
|
||||
user.TotpSecret = mfa.Secret
|
||||
if user.PreferredMfaType == "" {
|
||||
user.PreferredMfaType = mfa.Config.MfaType
|
||||
user.PreferredMfaType = mfa.MfaType
|
||||
}
|
||||
|
||||
_, err := updateUser(user.GetId(), user, columns)
|
||||
@ -121,14 +91,11 @@ func (mfa *TotpMfa) Enable(ctx *context.Context, user *User) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Input.CruSession.Delete(MfaRecoveryCodesSession)
|
||||
ctx.Input.CruSession.Delete(MfaTotpSecretSession)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mfa *TotpMfa) Verify(passcode string) error {
|
||||
result, err := totp.ValidateCustom(passcode, mfa.Config.Secret, time.Now().UTC(), totp.ValidateOpts{
|
||||
result, err := totp.ValidateCustom(passcode, mfa.Secret, time.Now().UTC(), totp.ValidateOpts{
|
||||
Period: MfaTotpPeriodInSeconds,
|
||||
Skew: 1,
|
||||
Digits: otp.DigitsSix,
|
||||
@ -153,7 +120,7 @@ func NewTotpMfaUtil(config *MfaProps) *TotpMfa {
|
||||
}
|
||||
|
||||
return &TotpMfa{
|
||||
Config: config,
|
||||
MfaProps: config,
|
||||
period: MfaTotpPeriodInSeconds,
|
||||
secretSize: 20,
|
||||
digits: otp.DigitsSix,
|
||||
|
@ -342,6 +342,11 @@ func GetDefaultApplication(id string) (*Application, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = extendApplicationWithSigninItems(defaultApplication)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return defaultApplication, nil
|
||||
}
|
||||
|
||||
|
@ -308,7 +308,7 @@ func BatchEnforce(permission *Permission, requests [][]string, permissionIds ...
|
||||
return enforcer.BatchEnforce(interfaceRequests)
|
||||
}
|
||||
|
||||
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([]string, error) {
|
||||
func getEnforcers(userId string) ([]*casbin.Enforcer, error) {
|
||||
permissions, _, err := getPermissionsAndRolesByUser(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -320,7 +320,8 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([
|
||||
}
|
||||
|
||||
for _, role := range allRoles {
|
||||
permissionsByRole, err := GetPermissionsByRole(role)
|
||||
var permissionsByRole []*Permission
|
||||
permissionsByRole, err = GetPermissionsByRole(role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -328,29 +329,45 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([
|
||||
permissions = append(permissions, permissionsByRole...)
|
||||
}
|
||||
|
||||
var values []string
|
||||
var enforcers []*casbin.Enforcer
|
||||
for _, permission := range permissions {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
var enforcer *casbin.Enforcer
|
||||
enforcer, err = getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values = append(values, fn(enforcer)...)
|
||||
enforcers = append(enforcers, enforcer)
|
||||
}
|
||||
|
||||
return values, nil
|
||||
return enforcers, nil
|
||||
}
|
||||
|
||||
func GetAllObjects(userId string) ([]string, error) {
|
||||
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
|
||||
return enforcer.GetAllObjects()
|
||||
})
|
||||
enforcers, err := getEnforcers(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := []string{}
|
||||
for _, enforcer := range enforcers {
|
||||
items := enforcer.GetAllObjects()
|
||||
res = append(res, items...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func GetAllActions(userId string) ([]string, error) {
|
||||
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
|
||||
return enforcer.GetAllActions()
|
||||
})
|
||||
enforcers, err := getEnforcers(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := []string{}
|
||||
for _, enforcer := range enforcers {
|
||||
items := enforcer.GetAllObjects()
|
||||
res = append(res, items...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func GetAllRoles(userId string) ([]string, error) {
|
||||
|
@ -312,8 +312,6 @@ func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) {
|
||||
} else {
|
||||
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *Provider) GetId() string {
|
||||
|
@ -116,7 +116,7 @@ func getFilteredWebhooks(webhooks []*Webhook, action string) []*Webhook {
|
||||
}
|
||||
|
||||
func SendWebhooks(record *casvisorsdk.Record) error {
|
||||
webhooks, err := getWebhooksByOrganization(record.Organization)
|
||||
webhooks, err := getWebhooksByOrganization("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -266,10 +266,9 @@ func (role *Role) GetId() string {
|
||||
}
|
||||
|
||||
func getRolesByUserInternal(userId string) ([]*Role, error) {
|
||||
roles := []*Role{}
|
||||
user, err := GetUser(userId)
|
||||
if err != nil {
|
||||
return roles, err
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, fmt.Errorf("The user: %s doesn't exist", userId)
|
||||
@ -280,9 +279,10 @@ func getRolesByUserInternal(userId string) ([]*Role, error) {
|
||||
query = query.Or("r.groups like ?", fmt.Sprintf("%%%s%%", group))
|
||||
}
|
||||
|
||||
roles := []*Role{}
|
||||
err = query.Find(&roles)
|
||||
if err != nil {
|
||||
return roles, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := []*Role{}
|
||||
@ -291,14 +291,13 @@ func getRolesByUserInternal(userId string) ([]*Role, error) {
|
||||
res = append(res, role)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getRolesByUser(userId string) ([]*Role, error) {
|
||||
roles, err := getRolesByUserInternal(userId)
|
||||
if err != nil {
|
||||
return roles, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allRolesIds := []string{}
|
||||
@ -379,15 +378,11 @@ func GetMaskedRoles(roles []*Role) []*Role {
|
||||
|
||||
// GetAncestorRoles returns a list of roles that contain the given roleIds
|
||||
func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
|
||||
var (
|
||||
result = []*Role{}
|
||||
roleMap = make(map[string]*Role)
|
||||
visited = make(map[string]bool)
|
||||
)
|
||||
if len(roleIds) == 0 {
|
||||
return result, nil
|
||||
return []*Role{}, nil
|
||||
}
|
||||
|
||||
visited := map[string]bool{}
|
||||
for _, roleId := range roleIds {
|
||||
visited[roleId] = true
|
||||
}
|
||||
@ -399,25 +394,26 @@ func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roleMap := map[string]*Role{}
|
||||
for _, r := range allRoles {
|
||||
roleMap[r.GetId()] = r
|
||||
}
|
||||
|
||||
// Second, find all the roles that contain father roles
|
||||
// find all the roles that contain father roles
|
||||
res := []*Role{}
|
||||
for _, r := range allRoles {
|
||||
isContain, ok := visited[r.GetId()]
|
||||
if isContain {
|
||||
result = append(result, r)
|
||||
res = append(res, r)
|
||||
} else if !ok {
|
||||
rId := r.GetId()
|
||||
visited[rId] = containsRole(r, roleMap, visited, roleIds...)
|
||||
if visited[rId] {
|
||||
result = append(result, r)
|
||||
res = append(res, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// containsRole is a helper function to check if a roles is related to any role in the given list roles
|
||||
|
@ -198,7 +198,7 @@ type Attribute struct {
|
||||
Values []string `xml:"AttributeValue"`
|
||||
}
|
||||
|
||||
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
|
||||
func GetSamlMeta(application *Application, host string, enablePostBinding bool) (*IdpEntityDescriptor, error) {
|
||||
cert, err := getCertByApplication(application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -217,6 +217,13 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
|
||||
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
|
||||
idpLocation := ""
|
||||
if enablePostBinding {
|
||||
idpLocation = fmt.Sprintf("%s/api/saml/redirect/%s/%s", originBackend, application.Owner, application.Name)
|
||||
} else {
|
||||
idpLocation = fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name)
|
||||
}
|
||||
|
||||
d := IdpEntityDescriptor{
|
||||
XMLName: xml.Name{
|
||||
Local: "md:EntityDescriptor",
|
||||
@ -248,7 +255,7 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
|
||||
},
|
||||
SingleSignOnService: SingleSignOnService{
|
||||
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
||||
Location: fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name),
|
||||
Location: idpLocation,
|
||||
},
|
||||
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
|
||||
},
|
||||
@ -442,3 +449,8 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
|
||||
|
||||
return samlResponse
|
||||
}
|
||||
|
||||
func GetSamlRedirectAddress(owner string, application string, relayState string, samlRequest string, host string) string {
|
||||
originF, _ := getOriginFromHost(host)
|
||||
return fmt.Sprintf("%s/login/saml/authorize/%s/%s?relayState=%s&samlRequest=%s", originF, owner, application, relayState, samlRequest)
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
|
||||
if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS {
|
||||
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
|
||||
} else if provider.Type == "Custom HTTP SMS" {
|
||||
client, err = newHttpSmsClient(provider.Endpoint, provider.Method, provider.Title)
|
||||
client, err = newHttpSmsClient(provider.Endpoint, provider.Method, provider.Title, provider.TemplateCode)
|
||||
} else {
|
||||
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
||||
}
|
||||
|
@ -27,20 +27,26 @@ type HttpSmsClient struct {
|
||||
endpoint string
|
||||
method string
|
||||
paramName string
|
||||
template string
|
||||
}
|
||||
|
||||
func newHttpSmsClient(endpoint string, method string, paramName string) (*HttpSmsClient, error) {
|
||||
func newHttpSmsClient(endpoint, method, paramName, template string) (*HttpSmsClient, error) {
|
||||
if template == "" {
|
||||
template = "%s"
|
||||
}
|
||||
client := &HttpSmsClient{
|
||||
endpoint: endpoint,
|
||||
method: method,
|
||||
paramName: paramName,
|
||||
template: template,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *HttpSmsClient) SendMessage(param map[string]string, targetPhoneNumber ...string) error {
|
||||
phoneNumber := targetPhoneNumber[0]
|
||||
content := param["code"]
|
||||
code := param["code"]
|
||||
content := fmt.Sprintf(c.template, code)
|
||||
|
||||
var req *http.Request
|
||||
var err error
|
||||
|
@ -43,7 +43,7 @@ type Syncer struct {
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
Password string `xorm:"varchar(150)" json:"password"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
Table string `xorm:"varchar(100)" json:"table"`
|
||||
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
|
||||
@ -116,22 +116,35 @@ func GetSyncer(id string) (*Syncer, error) {
|
||||
return getSyncer(owner, name)
|
||||
}
|
||||
|
||||
func GetMaskedSyncer(syncer *Syncer) *Syncer {
|
||||
func GetMaskedSyncer(syncer *Syncer, errs ...error) (*Syncer, error) {
|
||||
if len(errs) > 0 && errs[0] != nil {
|
||||
return nil, errs[0]
|
||||
}
|
||||
|
||||
if syncer == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if syncer.Password != "" {
|
||||
syncer.Password = "***"
|
||||
}
|
||||
return syncer
|
||||
return syncer, nil
|
||||
}
|
||||
|
||||
func GetMaskedSyncers(syncers []*Syncer) []*Syncer {
|
||||
for _, syncer := range syncers {
|
||||
syncer = GetMaskedSyncer(syncer)
|
||||
func GetMaskedSyncers(syncers []*Syncer, errs ...error) ([]*Syncer, error) {
|
||||
if len(errs) > 0 && errs[0] != nil {
|
||||
return nil, errs[0]
|
||||
}
|
||||
return syncers
|
||||
|
||||
var err error
|
||||
for _, syncer := range syncers {
|
||||
syncer, err = GetMaskedSyncer(syncer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return syncers, nil
|
||||
}
|
||||
|
||||
func UpdateSyncer(id string, syncer *Syncer) (bool, error) {
|
||||
|
@ -93,6 +93,8 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
|
||||
user.CreatedTime = value
|
||||
case "UpdatedTime":
|
||||
user.UpdatedTime = value
|
||||
case "DeletedTime":
|
||||
user.DeletedTime = value
|
||||
case "Id":
|
||||
user.Id = value
|
||||
case "Type":
|
||||
@ -266,6 +268,7 @@ func (syncer *Syncer) getMapFromOriginalUser(user *OriginalUser) map[string]stri
|
||||
m["Name"] = user.Name
|
||||
m["CreatedTime"] = user.CreatedTime
|
||||
m["UpdatedTime"] = user.UpdatedTime
|
||||
m["DeletedTime"] = user.DeletedTime
|
||||
m["Id"] = user.Id
|
||||
m["Type"] = user.Type
|
||||
m["Password"] = user.Password
|
||||
|
@ -186,6 +186,26 @@ func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func GetTokenByTokenValue(tokenValue string) (*Token, error) {
|
||||
token, err := GetTokenByAccessToken(tokenValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token != nil {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
token, err = GetTokenByRefreshToken(tokenValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token != nil {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func updateUsedByCode(token *Token) bool {
|
||||
affected, err := ormer.Engine.Where("code=?", token.Code).Cols("code_is_used").Update(token)
|
||||
if err != nil {
|
||||
@ -283,20 +303,6 @@ func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, e
|
||||
return affected != 0, application, token, nil
|
||||
}
|
||||
|
||||
func GetTokenByTokenAndApplication(token string, application string) (*Token, error) {
|
||||
tokenResult := Token{}
|
||||
existed, err := ormer.Engine.Where("(refresh_token = ? or access_token = ? ) and application = ?", token, token, application).Get(&tokenResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &tokenResult, nil
|
||||
}
|
||||
|
||||
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string, lang string) (string, *Application, error) {
|
||||
if responseType != "code" && responseType != "token" && responseType != "id_token" {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "token:Grant_type: %s is not supported in this application"), responseType), nil, nil
|
||||
|
@ -40,7 +40,7 @@ type UserShort struct {
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||
Email string `xorm:"varchar(100) index" json:"email"`
|
||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||
}
|
||||
|
||||
type UserWithoutThirdIdp struct {
|
||||
@ -48,10 +48,11 @@ type UserWithoutThirdIdp struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100) index" json:"createdTime"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
DeletedTime string `xorm:"varchar(100)" json:"deletedTime"`
|
||||
|
||||
Id string `xorm:"varchar(100) index" json:"id"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
Password string `xorm:"varchar(150)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
@ -62,7 +63,7 @@ type UserWithoutThirdIdp struct {
|
||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100) index" json:"email"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
@ -167,6 +168,7 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
||||
Name: user.Name,
|
||||
CreatedTime: user.CreatedTime,
|
||||
UpdatedTime: user.UpdatedTime,
|
||||
DeletedTime: user.DeletedTime,
|
||||
|
||||
Id: user.Id,
|
||||
Type: user.Type,
|
||||
|
@ -49,11 +49,12 @@ type User struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100) index" json:"createdTime"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
DeletedTime string `xorm:"varchar(100)" json:"deletedTime"`
|
||||
|
||||
Id string `xorm:"varchar(100) index" json:"id"`
|
||||
ExternalId string `xorm:"varchar(100) index" json:"externalId"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
Password string `xorm:"varchar(150)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
@ -64,7 +65,7 @@ type User struct {
|
||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100) index" json:"email"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
@ -183,6 +184,8 @@ type User struct {
|
||||
MfaPhoneEnabled bool `json:"mfaPhoneEnabled"`
|
||||
MfaEmailEnabled bool `json:"mfaEmailEnabled"`
|
||||
MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"`
|
||||
Invitation string `xorm:"varchar(100) index" json:"invitation"`
|
||||
InvitationCode string `xorm:"varchar(100) index" json:"invitationCode"`
|
||||
|
||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
@ -496,6 +499,24 @@ func GetUserByUserIdOnly(userId string) (*User, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserByInvitationCode(owner string, invitationCode string) (*User, error) {
|
||||
if owner == "" || invitationCode == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
user := User{Owner: owner, InvitationCode: invitationCode}
|
||||
existed, err := ormer.Engine.Get(&user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &user, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserByAccessKey(accessKey string) (*User, error) {
|
||||
if accessKey == "" {
|
||||
return nil, nil
|
||||
@ -619,7 +640,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
if len(columns) == 0 {
|
||||
columns = []string{
|
||||
"owner", "display_name", "avatar", "first_name", "last_name",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
||||
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret",
|
||||
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||
@ -638,6 +659,10 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
columns = append(columns, "updated_time")
|
||||
user.UpdatedTime = util.GetCurrentTime()
|
||||
|
||||
if len(user.DeletedTime) > 0 {
|
||||
columns = append(columns, "deleted_time")
|
||||
}
|
||||
|
||||
if util.ContainsString(columns, "groups") {
|
||||
_, err := userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
||||
if err != nil {
|
||||
@ -768,6 +793,13 @@ func AddUser(user *User) (bool, error) {
|
||||
}
|
||||
user.Ranking = int(count + 1)
|
||||
|
||||
if user.Groups != nil && len(user.Groups) > 0 {
|
||||
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.Insert(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -797,6 +829,13 @@ func AddUsers(users []*User) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if user.Groups != nil && len(user.Groups) > 0 {
|
||||
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.Insert(users)
|
||||
|
@ -41,11 +41,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, resp.Status)
|
||||
if resp.StatusCode == 404 {
|
||||
return nil, "", nil
|
||||
} else {
|
||||
return nil, "", fmt.Errorf("failed to download gravatar image: %s", resp.Status)
|
||||
}
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
// Get the content type and determine the file extension
|
||||
|
@ -1,10 +1,12 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
errors2 "errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/errors"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
@ -87,7 +89,7 @@ func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) {
|
||||
|
||||
users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group))
|
||||
if err != nil {
|
||||
if err == errors.ErrNameNotFound {
|
||||
if errors2.Is(err, errors.ErrNameNotFound) {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
|
@ -134,6 +134,7 @@ func UploadUsers(owner string, path string) (bool, error) {
|
||||
LastSigninIp: parseLineItem(&line, 38),
|
||||
Ldap: "",
|
||||
Properties: map[string]string{},
|
||||
DeletedTime: parseLineItem(&line, 39),
|
||||
}
|
||||
|
||||
if _, ok := oldUserMap[user.GetId()]; !ok {
|
||||
|
@ -73,10 +73,12 @@ func getObject(ctx *context.Context) (string, string) {
|
||||
}
|
||||
}
|
||||
|
||||
// query == "?id=built-in/admin"
|
||||
id := ctx.Input.Query("id")
|
||||
if id != "" {
|
||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
if !(strings.HasPrefix(ctx.Request.URL.Path, "/api/get-") && strings.HasSuffix(ctx.Request.URL.Path, "s")) {
|
||||
// query == "?id=built-in/admin"
|
||||
id := ctx.Input.Query("id")
|
||||
if id != "" {
|
||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
}
|
||||
}
|
||||
|
||||
owner := ctx.Input.Query("owner")
|
||||
@ -164,6 +166,10 @@ func getUrlPath(urlPath string) string {
|
||||
return "/api/webauthn"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(urlPath, "/api/saml/redirect") {
|
||||
return "/api/saml/redirect"
|
||||
}
|
||||
|
||||
return urlPath
|
||||
}
|
||||
|
||||
|
@ -24,16 +24,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
headerOrigin = "Origin"
|
||||
headerAllowOrigin = "Access-Control-Allow-Origin"
|
||||
headerAllowMethods = "Access-Control-Allow-Methods"
|
||||
headerAllowHeaders = "Access-Control-Allow-Headers"
|
||||
headerOrigin = "Origin"
|
||||
headerAllowOrigin = "Access-Control-Allow-Origin"
|
||||
headerAllowMethods = "Access-Control-Allow-Methods"
|
||||
headerAllowHeaders = "Access-Control-Allow-Headers"
|
||||
headerAllowCredentials = "Access-Control-Allow-Credentials"
|
||||
)
|
||||
|
||||
func setCorsHeaders(ctx *context.Context, origin string) {
|
||||
ctx.Output.Header(headerAllowOrigin, origin)
|
||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||
ctx.Output.Header(headerAllowCredentials, "true")
|
||||
|
||||
if ctx.Input.Method() == "OPTIONS" {
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
// Package routers
|
||||
// @APIVersion 1.376.1
|
||||
// @APIVersion 1.503.0
|
||||
// @Title Casdoor RESTful API
|
||||
// @Description Swagger Docs of Casdoor Backend API
|
||||
// @Contact casbin@googlegroups.com
|
||||
@ -60,7 +60,9 @@ func initAPI() {
|
||||
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
||||
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
||||
beego.Router("/api/saml/metadata", &controllers.ApiController{}, "GET:GetSamlMeta")
|
||||
beego.Router("/api/webhook", &controllers.ApiController{}, "POST:HandleOfficialAccountEvent")
|
||||
beego.Router("/api/saml/redirect/:owner/:application", &controllers.ApiController{}, "*:HandleSamlRedirect")
|
||||
beego.Router("/api/webhook", &controllers.ApiController{}, "*:HandleOfficialAccountEvent")
|
||||
beego.Router("/api/get-qrcode", &controllers.ApiController{}, "GET:GetQRCode")
|
||||
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
||||
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
||||
beego.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
|
||||
@ -93,6 +95,7 @@ func initAPI() {
|
||||
|
||||
beego.Router("/api/get-invitations", &controllers.ApiController{}, "GET:GetInvitations")
|
||||
beego.Router("/api/get-invitation", &controllers.ApiController{}, "GET:GetInvitation")
|
||||
beego.Router("/api/get-invitation-info", &controllers.ApiController{}, "GET:GetInvitationCodeInfo")
|
||||
beego.Router("/api/update-invitation", &controllers.ApiController{}, "POST:UpdateInvitation")
|
||||
beego.Router("/api/add-invitation", &controllers.ApiController{}, "POST:AddInvitation")
|
||||
beego.Router("/api/delete-invitation", &controllers.ApiController{}, "POST:DeleteInvitation")
|
||||
|
@ -34,6 +34,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
||||
return NewQiniuCloudKodoStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
case "Google Cloud Storage":
|
||||
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint)
|
||||
case "Synology":
|
||||
return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
31
storage/synology_nas.go
Normal file
31
storage/synology_nas.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2023 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 storage
|
||||
|
||||
import (
|
||||
"github.com/casdoor/oss"
|
||||
"github.com/casdoor/oss/synology"
|
||||
)
|
||||
|
||||
func NewSynologyNasStorageProvider(clientId string, clientSecret string, endpoint string) oss.StorageInterface {
|
||||
sp := synology.New(&synology.Config{
|
||||
AccessID: clientId,
|
||||
AccessKey: clientSecret,
|
||||
Endpoint: endpoint,
|
||||
SharedFolder: "/home",
|
||||
})
|
||||
|
||||
return sp
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@ swagger: "2.0"
|
||||
info:
|
||||
title: Casdoor RESTful API
|
||||
description: Swagger Docs of Casdoor Backend API
|
||||
version: 1.376.1
|
||||
version: 1.503.0
|
||||
contact:
|
||||
email: casbin@googlegroups.com
|
||||
basePath: /
|
||||
@ -31,6 +31,17 @@ paths:
|
||||
description: ""
|
||||
schema:
|
||||
$ref: '#/definitions/object.OidcDiscovery'
|
||||
/api/Callback:
|
||||
post:
|
||||
tags:
|
||||
- Callback API
|
||||
description: Get Login Error Counts
|
||||
operationId: ApiController.Callback
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/add-adapter:
|
||||
post:
|
||||
tags:
|
||||
@ -121,6 +132,24 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-invitation:
|
||||
post:
|
||||
tags:
|
||||
- Invitation API
|
||||
description: add invitation
|
||||
operationId: ApiController.AddInvitation
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the invitation
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Invitation'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-ldap:
|
||||
post:
|
||||
tags:
|
||||
@ -442,162 +471,10 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/api/Callback:
|
||||
post:
|
||||
tags:
|
||||
- Callback API
|
||||
description: Get Login Error Counts
|
||||
operationId: ApiController.Callback
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/api/get-captcha:
|
||||
get:
|
||||
tags:
|
||||
- Login API
|
||||
operationId: ApiController.GetCaptcha
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/api/get-captcha-status:
|
||||
get:
|
||||
tags:
|
||||
- Token API
|
||||
description: Get Login Error Counts
|
||||
operationId: ApiController.GetCaptchaStatus
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of user
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/api/get-webhook-event:
|
||||
get:
|
||||
tags:
|
||||
- GetWebhookEventType API
|
||||
operationId: ApiController.GetWebhookEventType
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/api/reset-email-or-phone:
|
||||
post:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.ResetEmailOrPhone
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/api/send-email:
|
||||
post:
|
||||
tags:
|
||||
- Service API
|
||||
description: This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
operationId: ApiController.SendEmail
|
||||
parameters:
|
||||
- in: query
|
||||
name: clientId
|
||||
description: The clientId of the application
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: clientSecret
|
||||
description: The clientSecret of the application
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: from
|
||||
description: Details of the email request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.EmailForm'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/api/send-notification:
|
||||
post:
|
||||
tags:
|
||||
- Service API
|
||||
description: This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
operationId: ApiController.SendNotification
|
||||
parameters:
|
||||
- in: body
|
||||
name: from
|
||||
description: Details of the notification request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.NotificationForm'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/api/send-sms:
|
||||
post:
|
||||
tags:
|
||||
- Service API
|
||||
description: This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
operationId: ApiController.SendSms
|
||||
parameters:
|
||||
- in: query
|
||||
name: clientId
|
||||
description: The clientId of the application
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: clientSecret
|
||||
description: The clientSecret of the application
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: from
|
||||
description: Details of the sms request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SmsForm'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/api/verify-code:
|
||||
post:
|
||||
tags:
|
||||
- Verification API
|
||||
operationId: ApiController.VerifyCode
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/api/webhook:
|
||||
post:
|
||||
tags:
|
||||
- HandleOfficialAccountEvent API
|
||||
operationId: ApiController.HandleOfficialAccountEvent
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/batch-enforce:
|
||||
post:
|
||||
tags:
|
||||
- Enforce API
|
||||
- Enforcer API
|
||||
description: Call Casbin BatchEnforce API
|
||||
operationId: ApiController.BatchEnforce
|
||||
parameters:
|
||||
@ -617,6 +494,10 @@ paths:
|
||||
name: modelId
|
||||
description: model id
|
||||
type: string
|
||||
- in: query
|
||||
name: owner
|
||||
description: owner
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
@ -744,6 +625,24 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/delete-invitation:
|
||||
post:
|
||||
tags:
|
||||
- Invitation API
|
||||
description: delete invitation
|
||||
operationId: ApiController.DeleteInvitation
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the invitation
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Invitation'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/delete-ldap:
|
||||
post:
|
||||
tags:
|
||||
@ -1064,7 +963,7 @@ paths:
|
||||
/api/enforce:
|
||||
post:
|
||||
tags:
|
||||
- Enforce API
|
||||
- Enforcer API
|
||||
description: Call Casbin Enforce API
|
||||
operationId: ApiController.Enforce
|
||||
parameters:
|
||||
@ -1088,6 +987,10 @@ paths:
|
||||
name: resourceId
|
||||
description: resource id
|
||||
type: string
|
||||
- in: query
|
||||
name: owner
|
||||
description: owner
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
@ -1213,6 +1116,33 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Application'
|
||||
/api/get-captcha:
|
||||
get:
|
||||
tags:
|
||||
- Login API
|
||||
operationId: ApiController.GetCaptcha
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/get-captcha-status:
|
||||
get:
|
||||
tags:
|
||||
- Token API
|
||||
description: Get Login Error Counts
|
||||
operationId: ApiController.GetCaptchaStatus
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of user
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/get-cert:
|
||||
get:
|
||||
tags:
|
||||
@ -1252,7 +1182,7 @@ paths:
|
||||
/api/get-dashboard:
|
||||
get:
|
||||
tags:
|
||||
- GetDashboard API
|
||||
- System API
|
||||
description: get information of dashboard
|
||||
operationId: ApiController.GetDashboard
|
||||
responses:
|
||||
@ -1410,6 +1340,42 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Group'
|
||||
/api/get-invitation:
|
||||
get:
|
||||
tags:
|
||||
- Invitation API
|
||||
description: get invitation
|
||||
operationId: ApiController.GetInvitation
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the invitation
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Invitation'
|
||||
/api/get-invitations:
|
||||
get:
|
||||
tags:
|
||||
- Invitation API
|
||||
description: get invitations
|
||||
operationId: ApiController.GetInvitations
|
||||
parameters:
|
||||
- in: query
|
||||
name: owner
|
||||
description: The owner of invitations
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Invitation'
|
||||
/api/get-ldap:
|
||||
get:
|
||||
tags:
|
||||
@ -1785,7 +1751,7 @@ paths:
|
||||
/api/get-prometheus-info:
|
||||
get:
|
||||
tags:
|
||||
- Prometheus API
|
||||
- System API
|
||||
description: get Prometheus Info
|
||||
operationId: ApiController.GetPrometheusInfo
|
||||
responses:
|
||||
@ -2269,6 +2235,16 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Webhook'
|
||||
/api/get-webhook-event:
|
||||
get:
|
||||
tags:
|
||||
- System API
|
||||
operationId: ApiController.GetWebhookEventType
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/get-webhooks:
|
||||
get:
|
||||
tags:
|
||||
@ -2396,8 +2372,50 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/login/oauth/access_token:
|
||||
post:
|
||||
tags:
|
||||
- Token API
|
||||
description: get OAuth access token
|
||||
operationId: ApiController.GetOAuthToken
|
||||
parameters:
|
||||
- in: query
|
||||
name: grant_type
|
||||
description: OAuth grant type
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: client_id
|
||||
description: OAuth client id
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: client_secret
|
||||
description: OAuth client secret
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: code
|
||||
description: OAuth code
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenWrapper'
|
||||
"400":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
"401":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
/api/login/oauth/introspect:
|
||||
post:
|
||||
tags:
|
||||
- Login API
|
||||
description: The introspection endpoint is an OAuth 2.0 endpoint that takes a
|
||||
operationId: ApiController.IntrospectToken
|
||||
parameters:
|
||||
@ -2543,6 +2561,16 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/reset-email-or-phone:
|
||||
post:
|
||||
tags:
|
||||
- Account API
|
||||
operationId: ApiController.ResetEmailOrPhone
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/run-syncer:
|
||||
get:
|
||||
tags:
|
||||
@ -2561,6 +2589,80 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/send-email:
|
||||
post:
|
||||
tags:
|
||||
- Service API
|
||||
description: This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
operationId: ApiController.SendEmail
|
||||
parameters:
|
||||
- in: query
|
||||
name: clientId
|
||||
description: The clientId of the application
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: clientSecret
|
||||
description: The clientSecret of the application
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: from
|
||||
description: Details of the email request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.EmailForm'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/send-notification:
|
||||
post:
|
||||
tags:
|
||||
- Service API
|
||||
description: This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
operationId: ApiController.SendNotification
|
||||
parameters:
|
||||
- in: body
|
||||
name: from
|
||||
description: Details of the notification request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.NotificationForm'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/send-sms:
|
||||
post:
|
||||
tags:
|
||||
- Service API
|
||||
description: This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
operationId: ApiController.SendSms
|
||||
parameters:
|
||||
- in: query
|
||||
name: clientId
|
||||
description: The clientId of the application
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: clientSecret
|
||||
description: The clientSecret of the application
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: from
|
||||
description: Details of the sms request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.SmsForm'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/send-verification-code:
|
||||
post:
|
||||
tags:
|
||||
@ -2778,6 +2880,29 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-invitation:
|
||||
post:
|
||||
tags:
|
||||
- Invitation API
|
||||
description: update invitation
|
||||
operationId: ApiController.UpdateInvitation
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the invitation
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the invitation
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Invitation'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-ldap:
|
||||
post:
|
||||
tags:
|
||||
@ -3245,6 +3370,33 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/verify-code:
|
||||
post:
|
||||
tags:
|
||||
- Verification API
|
||||
operationId: ApiController.VerifyCode
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
/api/verify-invitation:
|
||||
get:
|
||||
tags:
|
||||
- Invitation API
|
||||
description: verify invitation
|
||||
operationId: ApiController.VerifyInvitation
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the invitation
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/webauthn/signin/begin:
|
||||
get:
|
||||
tags:
|
||||
@ -3314,46 +3466,16 @@ paths:
|
||||
description: '"The Response object"'
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/apiapi/login/oauth/access_token:
|
||||
/api/webhook:
|
||||
post:
|
||||
tags:
|
||||
- Token API
|
||||
description: get OAuth access token
|
||||
operationId: ApiController.GetOAuthToken
|
||||
parameters:
|
||||
- in: query
|
||||
name: grant_type
|
||||
description: OAuth grant type
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: client_id
|
||||
description: OAuth client id
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: client_secret
|
||||
description: OAuth client secret
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: code
|
||||
description: OAuth code
|
||||
required: true
|
||||
type: string
|
||||
- System API
|
||||
operationId: ApiController.HandleOfficialAccountEvent
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenWrapper'
|
||||
"400":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
"401":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenError'
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
definitions:
|
||||
casbin.Enforcer:
|
||||
title: Enforcer
|
||||
@ -3546,10 +3668,10 @@ definitions:
|
||||
expireInHours:
|
||||
type: integer
|
||||
format: int64
|
||||
failedSigninLimit:
|
||||
failedSigninFrozenTime:
|
||||
type: integer
|
||||
format: int64
|
||||
failedSigninfrozenTime:
|
||||
failedSigninLimit:
|
||||
type: integer
|
||||
format: int64
|
||||
forgetUrl:
|
||||
@ -3606,6 +3728,10 @@ definitions:
|
||||
type: string
|
||||
signinHtml:
|
||||
type: string
|
||||
signinMethods:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.SigninMethod'
|
||||
signinUrl:
|
||||
type: string
|
||||
signupHtml:
|
||||
@ -3624,6 +3750,10 @@ definitions:
|
||||
type: string
|
||||
themeData:
|
||||
$ref: '#/definitions/object.ThemeData'
|
||||
tokenFields:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
tokenFormat:
|
||||
type: string
|
||||
object.Cert:
|
||||
@ -3780,6 +3910,40 @@ definitions:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
object.Invitation:
|
||||
title: Invitation
|
||||
type: object
|
||||
properties:
|
||||
application:
|
||||
type: string
|
||||
code:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
quota:
|
||||
type: integer
|
||||
format: int64
|
||||
signupGroup:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
updatedTime:
|
||||
type: string
|
||||
usedCount:
|
||||
type: integer
|
||||
format: int64
|
||||
username:
|
||||
type: string
|
||||
object.Ldap:
|
||||
title: Ldap
|
||||
type: object
|
||||
@ -4451,10 +4615,20 @@ definitions:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
nameformat:
|
||||
nameFormat:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
object.SigninMethod:
|
||||
title: SigninMethod
|
||||
type: object
|
||||
properties:
|
||||
displayName:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
rule:
|
||||
type: string
|
||||
object.SignupItem:
|
||||
title: SignupItem
|
||||
type: object
|
||||
@ -4467,6 +4641,8 @@ definitions:
|
||||
type: string
|
||||
prompted:
|
||||
type: boolean
|
||||
regex:
|
||||
type: string
|
||||
required:
|
||||
type: boolean
|
||||
rule:
|
||||
@ -4724,6 +4900,8 @@ definitions:
|
||||
type: string
|
||||
deezer:
|
||||
type: string
|
||||
deletedTime:
|
||||
type: string
|
||||
digitalocean:
|
||||
type: string
|
||||
dingtalk:
|
||||
|
32
util/json.go
32
util/json.go
@ -14,7 +14,10 @@
|
||||
|
||||
package util
|
||||
|
||||
import "encoding/json"
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func StructToJson(v interface{}) string {
|
||||
data, err := json.Marshal(v)
|
||||
@ -37,3 +40,30 @@ func StructToJsonFormatted(v interface{}) string {
|
||||
func JsonToStruct(data string, v interface{}) error {
|
||||
return json.Unmarshal([]byte(data), v)
|
||||
}
|
||||
|
||||
func TryJsonToAnonymousStruct(j string) (interface{}, error) {
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(j), &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a slice of StructFields
|
||||
fields := make([]reflect.StructField, 0, len(data))
|
||||
for k, v := range data {
|
||||
fields = append(fields, reflect.StructField{
|
||||
Name: k,
|
||||
Type: reflect.TypeOf(v),
|
||||
})
|
||||
}
|
||||
|
||||
// Create the struct type
|
||||
t := reflect.StructOf(fields)
|
||||
|
||||
// Unmarshal again, this time to the new struct type
|
||||
val := reflect.New(t)
|
||||
i := val.Interface()
|
||||
if err := json.Unmarshal([]byte(j), &i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -40,7 +39,7 @@ func GetPath(path string) string {
|
||||
func ListFiles(path string) []string {
|
||||
res := []string{}
|
||||
|
||||
files, err := ioutil.ReadDir(path)
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -14,10 +14,10 @@
|
||||
|
||||
package util
|
||||
|
||||
import "io/ioutil"
|
||||
import "os"
|
||||
|
||||
func GetUploadXlsxPath(fileId string) string {
|
||||
file, err := ioutil.TempFile("", fileId)
|
||||
file, err := os.CreateTemp("", fileId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -324,9 +324,16 @@ func GetUsernameFromEmail(email string) string {
|
||||
}
|
||||
|
||||
func StringToInterfaceArray(array []string) []interface{} {
|
||||
var interfaceArray []interface{}
|
||||
for _, v := range array {
|
||||
interfaceArray = append(interfaceArray, v)
|
||||
var (
|
||||
interfaceArray []interface{}
|
||||
elem interface{}
|
||||
)
|
||||
for _, elem = range array {
|
||||
jStruct, err := TryJsonToAnonymousStruct(elem.(string))
|
||||
if err == nil {
|
||||
elem = jStruct
|
||||
}
|
||||
interfaceArray = append(interfaceArray, elem)
|
||||
}
|
||||
return interfaceArray
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user