mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-17 00:03:50 +08:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
5846e337c7 | |||
44f4de1440 | |||
27adeb4620 | |||
5c107db43b | |||
27187b3a54 | |||
14fcedcc5d | |||
e7c015f288 | |||
c4819602ec | |||
dea03cdd15 | |||
21f394847e | |||
9bef9691fb | |||
141f22a707 | |||
02329d342a | |||
b9d3e2184c | |||
28caf8550e | |||
79159dc809 | |||
63081641d6 | |||
698f24f762 | |||
5499e62d7f | |||
f8905ae64c | |||
a42594859f | |||
46e0bc1a39 | |||
ffe2330238 | |||
ec53616dc8 | |||
067276d739 | |||
468ceb6b71 | |||
b31a317585 | |||
396b6fb65f | |||
be637fca81 | |||
374928e719 | |||
5c103e8cd3 | |||
85b86e8831 | |||
08864686f3 | |||
dc06eb9948 | |||
b068202e74 | |||
cb16567c7b | |||
4eb725d47a | |||
ce72a172b0 | |||
5521962e0c | |||
37b8b09cc0 | |||
482eb61168 | |||
8819a8697b | |||
85cb68eb66 | |||
b25b5f0249 | |||
947dcf6e75 | |||
113c27db73 | |||
badfe34755 | |||
a5f9f61381 | |||
2ce8c93ead | |||
da41ac7275 | |||
fd0c70a827 | |||
c4a6f07672 | |||
a67f541171 | |||
192968bac8 | |||
23d4488b64 | |||
23f4684e1d | |||
1a91e7b0f9 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
- run: yarn install && CI=false yarn run build
|
||||
@ -101,7 +101,7 @@ jobs:
|
||||
working-directory: ./
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
- run: yarn install
|
||||
@ -137,7 +137,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- name: Fetch Previous version
|
||||
id: get-previous-tag
|
||||
|
40
.github/workflows/helm.yml
vendored
Normal file
40
.github/workflows/helm.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Helm Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'manifests/casdoor/Chart.yaml'
|
||||
|
||||
jobs:
|
||||
release-helm-chart:
|
||||
name: Release Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Release Helm Chart
|
||||
run: |
|
||||
cd manifests/casdoor
|
||||
REGISTRY=oci://registry-1.docker.io/casbin
|
||||
helm package .
|
||||
PKG_NAME=$(ls *.tgz)
|
||||
helm repo index . --url $REGISTRY --merge index.yaml
|
||||
helm push $PKG_NAME $REGISTRY
|
||||
rm $PKG_NAME
|
||||
|
||||
- name: Commit updated helm index.yaml
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: 'ci: update helm index.yaml'
|
@ -1,10 +1,10 @@
|
||||
FROM node:16.18.0 AS FRONT
|
||||
FROM node:18.19.0 AS FRONT
|
||||
WORKDIR /web
|
||||
COPY ./web .
|
||||
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
||||
|
||||
|
||||
FROM golang:1.19.9 AS BACK
|
||||
FROM golang:1.20.12 AS BACK
|
||||
WORKDIR /go/src/casdoor
|
||||
COPY . .
|
||||
RUN ./build.sh
|
||||
|
14
README.md
14
README.md
@ -42,6 +42,20 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<sup>Sponsored by</sup>
|
||||
<br>
|
||||
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.casbin.org/img/stytch-white.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://cdn.casbin.org/img/stytch-charcoal.png">
|
||||
<img src="https://cdn.casbin.org/img/stytch-charcoal.png" width="275">
|
||||
</picture>
|
||||
</a><br/>
|
||||
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin"><b>Build auth with fraud prevention, faster.</b><br/> Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.</a>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
## Online demo
|
||||
|
||||
- Read-only site: https://door.casdoor.com (any modification operation will fail)
|
||||
|
@ -92,6 +92,9 @@ p, *, *, GET, /api/get-plan, *, *
|
||||
p, *, *, GET, /api/get-subscription, *, *
|
||||
p, *, *, GET, /api/get-provider, *, *
|
||||
p, *, *, GET, /api/get-organization-names, *, *
|
||||
p, *, *, GET, /api/get-all-objects, *, *
|
||||
p, *, *, GET, /api/get-all-actions, *, *
|
||||
p, *, *, GET, /api/get-all-roles, *, *
|
||||
`
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
@ -147,11 +150,11 @@ 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" {
|
||||
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/") {
|
||||
return true
|
||||
} else if urlPath == "/api/update-user" {
|
||||
// Allow ordinary users to update their own information
|
||||
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") {
|
||||
if (subOwner == objOwner && subName == objName || subOwner == "app") && !(subOwner == "built-in" && subName == "admin") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -56,6 +56,17 @@ type Captcha struct {
|
||||
SubType string `json:"subType"`
|
||||
}
|
||||
|
||||
// this API is used by "Api URL" of Flarum's FoF Passport plugin
|
||||
// https://github.com/FriendsOfFlarum/passport
|
||||
type LaravelResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
EmailVerifiedAt string `json:"email_verified_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Signup
|
||||
// @Tag Login API
|
||||
// @Title Signup
|
||||
@ -238,7 +249,7 @@ func (c *ApiController) Signup() {
|
||||
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
|
||||
// @Param state query string false "state"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /logout [get,post]
|
||||
// @router /logout [post]
|
||||
func (c *ApiController) Logout() {
|
||||
// https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html
|
||||
accessToken := c.Input().Get("id_token_hint")
|
||||
@ -282,17 +293,15 @@ func (c *ApiController) Logout() {
|
||||
return
|
||||
}
|
||||
|
||||
affected, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
||||
_, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !affected {
|
||||
if token == nil {
|
||||
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
|
||||
return
|
||||
@ -319,7 +328,15 @@ func (c *ApiController) Logout() {
|
||||
return
|
||||
} else {
|
||||
if application.IsRedirectUriValid(redirectUri) {
|
||||
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
|
||||
redirectUrl := redirectUri
|
||||
if state != "" {
|
||||
if strings.Contains(redirectUri, "?") {
|
||||
redirectUrl = fmt.Sprintf("%s&state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||
} else {
|
||||
redirectUrl = fmt.Sprintf("%s?state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||
}
|
||||
}
|
||||
c.Ctx.Redirect(http.StatusFound, redirectUrl)
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||
return
|
||||
@ -412,7 +429,7 @@ func (c *ApiController) GetUserinfo() {
|
||||
// @Title UserInfo2
|
||||
// @Tag Account API
|
||||
// @Description return Laravel compatible user information according to OAuth 2.0
|
||||
// @Success 200 {object} LaravelResponse The Response object
|
||||
// @Success 200 {object} controllers.LaravelResponse The Response object
|
||||
// @router /user [get]
|
||||
func (c *ApiController) GetUserinfo2() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
@ -420,17 +437,6 @@ func (c *ApiController) GetUserinfo2() {
|
||||
return
|
||||
}
|
||||
|
||||
// this API is used by "Api URL" of Flarum's FoF Passport plugin
|
||||
// https://github.com/FriendsOfFlarum/passport
|
||||
type LaravelResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
EmailVerifiedAt string `json:"email_verified_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
response := LaravelResponse{
|
||||
Id: user.Id,
|
||||
Name: user.Name,
|
||||
@ -448,6 +454,7 @@ func (c *ApiController) GetUserinfo2() {
|
||||
// @Tag Login API
|
||||
// @Title GetCaptcha
|
||||
// @router /api/get-captcha [get]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) GetCaptcha() {
|
||||
applicationId := c.Input().Get("applicationId")
|
||||
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
||||
@ -473,7 +480,7 @@ func (c *ApiController) GetCaptcha() {
|
||||
Type: captchaProvider.Type,
|
||||
SubType: captchaProvider.SubType,
|
||||
ClientId: captchaProvider.ClientId,
|
||||
ClientSecret: captchaProvider.ClientSecret,
|
||||
ClientSecret: "***",
|
||||
ClientId2: captchaProvider.ClientId2,
|
||||
ClientSecret2: captchaProvider.ClientSecret2,
|
||||
})
|
||||
|
@ -110,6 +110,14 @@ 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))
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
|
||||
} else {
|
||||
scope := c.Input().Get("scope")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
|
||||
nonce := c.Input().Get("nonce")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
|
||||
resp = tokenToResponse(token)
|
||||
}
|
||||
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||
@ -221,7 +222,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
// @Param redirectUri query string true "redirect uri"
|
||||
// @Param scope query string true "scope"
|
||||
// @Param state query string true "state"
|
||||
// @Success 200 {object} Response The Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-app-login [get]
|
||||
func (c *ApiController) GetApplicationLogin() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
@ -386,6 +387,16 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if enableCaptcha {
|
||||
captchaProvider, err := object.GetCaptchaProviderByApplication(util.GetId(application.Owner, application.Name), "false", c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider.Type != "Default" {
|
||||
authForm.ClientSecret = captchaProvider.ClientSecret
|
||||
}
|
||||
|
||||
var isHuman bool
|
||||
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||
if err != nil {
|
||||
@ -876,6 +887,7 @@ func (c *ApiController) HandleSamlLogin() {
|
||||
// @Tag HandleOfficialAccountEvent API
|
||||
// @Title HandleOfficialAccountEvent
|
||||
// @router /api/webhook [POST]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) HandleOfficialAccountEvent() {
|
||||
respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
@ -906,6 +918,7 @@ func (c *ApiController) HandleOfficialAccountEvent() {
|
||||
// @Tag GetWebhookEventType API
|
||||
// @Title GetWebhookEventType
|
||||
// @router /api/get-webhook-event [GET]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) GetWebhookEventType() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
@ -935,8 +948,14 @@ func (c *ApiController) GetCaptchaStatus() {
|
||||
return
|
||||
}
|
||||
|
||||
failedSigninLimit, _, err := object.GetFailedSigninConfigByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var captchaEnabled bool
|
||||
if user != nil && user.SigninWrongTimes >= object.SigninWrongTimesLimit {
|
||||
if user != nil && user.SigninWrongTimes >= failedSigninLimit {
|
||||
captchaEnabled = true
|
||||
}
|
||||
c.ResponseOk(captchaEnabled)
|
||||
@ -947,6 +966,7 @@ func (c *ApiController) GetCaptchaStatus() {
|
||||
// @Tag Callback API
|
||||
// @Description Get Login Error Counts
|
||||
// @router /api/Callback [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) Callback() {
|
||||
code := c.GetString("code")
|
||||
state := c.GetString("state")
|
||||
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -25,7 +26,7 @@ import (
|
||||
// @Title Enforce
|
||||
// @Tag Enforce API
|
||||
// @Description Call Casbin Enforce API
|
||||
// @Param body body object.CasbinRequest true "Casbin request"
|
||||
// @Param body body []string true "Casbin request"
|
||||
// @Param permissionId query string false "permission id"
|
||||
// @Param modelId query string false "model id"
|
||||
// @Param resourceId query string false "resource id"
|
||||
@ -42,7 +43,7 @@ func (c *ApiController) Enforce() {
|
||||
return
|
||||
}
|
||||
|
||||
var request object.CasbinRequest
|
||||
var request []string
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
@ -56,13 +57,22 @@ func (c *ApiController) Enforce() {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := enforcer.Enforce(request...)
|
||||
res := []bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
// type transformation
|
||||
interfaceRequest := util.StringToInterfaceArray(request)
|
||||
|
||||
enforceResult, err := enforcer.Enforce(interfaceRequest...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
@ -72,22 +82,24 @@ func (c *ApiController) Enforce() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
|
||||
if permission == nil {
|
||||
res = append(res, false)
|
||||
} else {
|
||||
enforceResult, err := object.Enforce(permission, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
c.ResponseError(fmt.Sprintf("permission: %s doesn't exist", permissionId))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
res := []bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
enforceResult, err := object.Enforce(permission, request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, permission.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
@ -111,32 +123,33 @@ func (c *ApiController) Enforce() {
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
|
||||
keyRes := []string{}
|
||||
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||
for _, permissionIds := range listPermissionIdMap {
|
||||
for key, permissionIds := range listPermissionIdMap {
|
||||
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
enforceResult, err := object.Enforce(firstPermission, &request, permissionIds...)
|
||||
enforceResult, err := object.Enforce(firstPermission, request, permissionIds...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, key)
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
c.ResponseOk(res, keyRes)
|
||||
}
|
||||
|
||||
// BatchEnforce
|
||||
// @Title BatchEnforce
|
||||
// @Tag Enforce API
|
||||
// @Description Call Casbin BatchEnforce API
|
||||
// @Param body body object.CasbinRequest true "array of casbin requests"
|
||||
// @Param body body []string true "array of casbin requests"
|
||||
// @Param permissionId query string false "permission id"
|
||||
// @Param modelId query string false "model id"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
@ -146,7 +159,7 @@ func (c *ApiController) BatchEnforce() {
|
||||
modelId := c.Input().Get("modelId")
|
||||
enforcerId := c.Input().Get("enforcerId")
|
||||
|
||||
var requests []object.CasbinRequest
|
||||
var requests [][]string
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
@ -160,13 +173,22 @@ func (c *ApiController) BatchEnforce() {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := enforcer.BatchEnforce(requests)
|
||||
res := [][]bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
// type transformation
|
||||
interfaceRequests := util.StringToInterfaceArray2d(requests)
|
||||
|
||||
enforceResult, err := enforcer.BatchEnforce(interfaceRequests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
@ -176,28 +198,24 @@ func (c *ApiController) BatchEnforce() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
|
||||
if permission == nil {
|
||||
l := len(requests)
|
||||
resRequest := make([]bool, l)
|
||||
for i := 0; i < l; i++ {
|
||||
resRequest[i] = false
|
||||
}
|
||||
|
||||
res = append(res, resRequest)
|
||||
} else {
|
||||
enforceResult, err := object.BatchEnforce(permission, &requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
c.ResponseError(fmt.Sprintf("permission: %s doesn't exist", permissionId))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
res := [][]bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
enforceResult, err := object.BatchEnforce(permission, requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, permission.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
@ -215,7 +233,7 @@ func (c *ApiController) BatchEnforce() {
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
|
||||
keyRes := []string{}
|
||||
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||
for _, permissionIds := range listPermissionIdMap {
|
||||
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||
@ -224,16 +242,17 @@ func (c *ApiController) BatchEnforce() {
|
||||
return
|
||||
}
|
||||
|
||||
enforceResult, err := object.BatchEnforce(firstPermission, &requests, permissionIds...)
|
||||
enforceResult, err := object.BatchEnforce(firstPermission, requests, permissionIds...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, firstPermission.GetModelAndAdapter())
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
c.ResponseOk(res, keyRes)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllObjects() {
|
||||
@ -243,7 +262,13 @@ func (c *ApiController) GetAllObjects() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllObjects(userId))
|
||||
objects, err := object.GetAllObjects(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(objects)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllActions() {
|
||||
@ -253,7 +278,13 @@ func (c *ApiController) GetAllActions() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllActions(userId))
|
||||
actions, err := object.GetAllActions(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(actions)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllRoles() {
|
||||
@ -263,5 +294,11 @@ func (c *ApiController) GetAllRoles() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllRoles(userId))
|
||||
roles, err := object.GetAllRoles(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(roles)
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ func (c *ApiController) GetEnforcers() {
|
||||
// @Tag Enforcer API
|
||||
// @Description get enforcer
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Success 200 {object} object
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /get-enforcer [get]
|
||||
func (c *ApiController) GetEnforcer() {
|
||||
id := c.Input().Get("id")
|
||||
@ -99,7 +99,7 @@ func (c *ApiController) GetEnforcer() {
|
||||
// @Description update enforcer
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Param enforcer body object true "The enforcer object"
|
||||
// @Success 200 {object} object
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /update-enforcer [post]
|
||||
func (c *ApiController) UpdateEnforcer() {
|
||||
id := c.Input().Get("id")
|
||||
@ -120,7 +120,7 @@ func (c *ApiController) UpdateEnforcer() {
|
||||
// @Tag Enforcer API
|
||||
// @Description add enforcer
|
||||
// @Param enforcer body object true "The enforcer object"
|
||||
// @Success 200 {object} object
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /add-enforcer [post]
|
||||
func (c *ApiController) AddEnforcer() {
|
||||
enforcer := object.Enforcer{}
|
||||
@ -138,8 +138,8 @@ func (c *ApiController) AddEnforcer() {
|
||||
// @Title DeleteEnforcer
|
||||
// @Tag Enforcer API
|
||||
// @Description delete enforcer
|
||||
// @Param body body object.Enforce true "The enforcer object"
|
||||
// @Success 200 {object} object
|
||||
// @Param body body object.Enforcer true "The enforcer object"
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /delete-enforcer [post]
|
||||
func (c *ApiController) DeleteEnforcer() {
|
||||
var enforcer object.Enforcer
|
||||
|
@ -42,7 +42,7 @@ type LdapSyncResp struct {
|
||||
// @Tag Account API
|
||||
// @Description get ldap users
|
||||
// Param id string true "id"
|
||||
// @Success 200 {object} LdapResp The Response object
|
||||
// @Success 200 {object} controllers.LdapResp The Response object
|
||||
// @router /get-ldap-users [get]
|
||||
func (c *ApiController) GetLdapUsers() {
|
||||
id := c.Input().Get("id")
|
||||
@ -59,6 +59,7 @@ func (c *ApiController) GetLdapUsers() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||
//if err != nil {
|
||||
@ -249,7 +250,7 @@ func (c *ApiController) DeleteLdap() {
|
||||
// @Tag Account API
|
||||
// @Description sync ldap users
|
||||
// @Param id query string true "id"
|
||||
// @Success 200 {object} LdapSyncResp The Response object
|
||||
// @Success 200 {object} controllers.LdapSyncResp The Response object
|
||||
// @router /sync-ldap-users [post]
|
||||
func (c *ApiController) SyncLdapUsers() {
|
||||
id := c.Input().Get("id")
|
||||
|
@ -26,8 +26,10 @@ type LinkForm struct {
|
||||
}
|
||||
|
||||
// Unlink ...
|
||||
// @router /unlink [post]
|
||||
// @Tag Login API
|
||||
// @Title Unlink
|
||||
// @router /unlink [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) Unlink() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
|
@ -73,7 +73,7 @@ func (c *ApiController) MfaSetupInitiate() {
|
||||
// @Description setup verify totp
|
||||
// @param secret form string true "MFA secret"
|
||||
// @param passcode form string true "MFA passcode"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /mfa/setup/verify [post]
|
||||
func (c *ApiController) MfaSetupVerify() {
|
||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||
@ -104,7 +104,7 @@ func (c *ApiController) MfaSetupVerify() {
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @param type form string true "MFA auth type"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /mfa/setup/enable [post]
|
||||
func (c *ApiController) MfaSetupEnable() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
@ -143,7 +143,7 @@ func (c *ApiController) MfaSetupEnable() {
|
||||
// @Description: Delete MFA
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-mfa/ [post]
|
||||
func (c *ApiController) DeleteMfa() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
@ -176,7 +176,7 @@ func (c *ApiController) DeleteMfa() {
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @param id form string true "id of user's MFA props"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /set-preferred-mfa [post]
|
||||
func (c *ApiController) SetPreferredMfa() {
|
||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||
|
@ -178,7 +178,7 @@ func (c *ApiController) DeleteOrganization() {
|
||||
// @Tag Organization API
|
||||
// @Description get default application
|
||||
// @Param id query string true "organization id"
|
||||
// @Success 200 {object} Response The Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-default-application [get]
|
||||
func (c *ApiController) GetDefaultApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
|
@ -51,9 +51,14 @@ type NotificationForm struct {
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param from body controllers.EmailForm true "Details of the email request"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/send-email [post]
|
||||
func (c *ApiController) SendEmail() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var emailForm EmailForm
|
||||
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||
@ -108,8 +113,13 @@ func (c *ApiController) SendEmail() {
|
||||
}
|
||||
|
||||
code := "123456"
|
||||
|
||||
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||
content := fmt.Sprintf(emailForm.Content, code)
|
||||
content := strings.Replace(provider.Content, "%s", code, 1)
|
||||
if user != nil {
|
||||
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
|
||||
}
|
||||
|
||||
for _, receiver := range emailForm.Receivers {
|
||||
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
|
||||
if err != nil {
|
||||
@ -128,7 +138,7 @@ func (c *ApiController) SendEmail() {
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param from body controllers.SmsForm true "Details of the sms request"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/send-sms [post]
|
||||
func (c *ApiController) SendSms() {
|
||||
provider, err := c.GetProviderFromContext("SMS")
|
||||
@ -166,7 +176,7 @@ func (c *ApiController) SendSms() {
|
||||
// @Tag Service API
|
||||
// @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} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/send-notification [post]
|
||||
func (c *ApiController) SendNotification() {
|
||||
provider, err := c.GetProviderFromContext("Notification")
|
||||
|
@ -510,6 +510,7 @@ func (c *ApiController) SetPassword() {
|
||||
// @Title CheckUserPassword
|
||||
// @router /check-user-password [post]
|
||||
// @Tag User API
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) CheckUserPassword() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
@ -580,6 +581,7 @@ func (c *ApiController) GetUserCount() {
|
||||
// @Title AddUserKeys
|
||||
// @router /add-user-keys [post]
|
||||
// @Tag User API
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) AddUserKeys() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
|
@ -39,6 +39,7 @@ const (
|
||||
// @Title SendVerificationCode
|
||||
// @Tag Verification API
|
||||
// @router /send-verification-code [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) SendVerificationCode() {
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
@ -53,17 +54,34 @@ func (c *ApiController) SendVerificationCode() {
|
||||
return
|
||||
}
|
||||
|
||||
if vform.CaptchaType != "none" {
|
||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||
return
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if !isHuman {
|
||||
provider, err := object.GetCaptchaProviderByApplication(vform.ApplicationId, "false", c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if provider != nil {
|
||||
if vform.CaptchaType != provider.Type {
|
||||
c.ResponseError(c.T("verification:Turing test failed."))
|
||||
return
|
||||
}
|
||||
|
||||
if provider.Type != "Default" {
|
||||
vform.ClientSecret = provider.ClientSecret
|
||||
}
|
||||
|
||||
if vform.CaptchaType != "none" {
|
||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||
return
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if !isHuman {
|
||||
c.ResponseError(c.T("verification:Turing test failed."))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application, err := object.GetApplication(vform.ApplicationId)
|
||||
@ -212,6 +230,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
// @Title VerifyCaptcha
|
||||
// @Tag Verification API
|
||||
// @router /verify-captcha [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) VerifyCaptcha() {
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
@ -225,6 +244,16 @@ func (c *ApiController) VerifyCaptcha() {
|
||||
return
|
||||
}
|
||||
|
||||
captchaProvider, err := object.GetCaptchaProviderByOwnerName(vform.ApplicationId, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider.Type != "Default" {
|
||||
vform.ClientSecret = captchaProvider.ClientSecret
|
||||
}
|
||||
|
||||
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||
@ -244,6 +273,7 @@ func (c *ApiController) VerifyCaptcha() {
|
||||
// @Tag Account API
|
||||
// @Title ResetEmailOrPhone
|
||||
// @router /api/reset-email-or-phone [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) ResetEmailOrPhone() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
@ -338,6 +368,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
// @Tag Verification API
|
||||
// @Title VerifyCode
|
||||
// @router /api/verify-code [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) VerifyCode() {
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
|
@ -146,7 +146,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
|
||||
}
|
||||
|
||||
// WebAuthnSigninFinish
|
||||
// @Title WebAuthnSigninBegin
|
||||
// @Title WebAuthnSigninFinish
|
||||
// @Tag Login API
|
||||
// @Description WebAuthn Login Flow 2nd stage
|
||||
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
|
||||
|
82
email/custom_http.go
Normal file
82
email/custom_http.go
Normal file
@ -0,0 +1,82 @@
|
||||
// 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 email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
type HttpEmailProvider struct {
|
||||
endpoint string
|
||||
method string
|
||||
}
|
||||
|
||||
func NewHttpEmailProvider(endpoint string, method string) *HttpEmailProvider {
|
||||
client := &HttpEmailProvider{
|
||||
endpoint: endpoint,
|
||||
method: method,
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
||||
var req *http.Request
|
||||
var err error
|
||||
if c.method == "POST" {
|
||||
formValues := url.Values{}
|
||||
formValues.Set("fromName", fromName)
|
||||
formValues.Set("toAddress", toAddress)
|
||||
formValues.Set("subject", subject)
|
||||
formValues.Set("content", content)
|
||||
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else if c.method == "GET" {
|
||||
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("fromName", fromName)
|
||||
q.Add("toAddress", toAddress)
|
||||
q.Add("subject", subject)
|
||||
q.Add("content", content)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
} else {
|
||||
return fmt.Errorf("HttpEmailProvider's Send() error, unsupported method: %s", c.method)
|
||||
}
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("HttpEmailProvider's Send() error, custom HTTP Email request failed with status: %s", resp.Status)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -18,9 +18,11 @@ type EmailProvider interface {
|
||||
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
|
||||
}
|
||||
|
||||
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool) EmailProvider {
|
||||
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool, endpoint string, method string) EmailProvider {
|
||||
if typ == "Azure ACS" {
|
||||
return NewAzureACSEmailProvider(clientSecret, host)
|
||||
} else if typ == "Custom HTTP Email" {
|
||||
return NewHttpEmailProvider(endpoint, method)
|
||||
} else {
|
||||
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
||||
}
|
||||
|
26
go.mod
26
go.mod
@ -4,17 +4,15 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/Masterminds/squirrel v1.5.3
|
||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||
github.com/aws/aws-sdk-go v1.45.5
|
||||
github.com/beego/beego v1.12.12
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/casbin/casbin v1.9.1 // indirect
|
||||
github.com/casbin/casbin/v2 v2.77.2
|
||||
github.com/casdoor/go-sms-sender v0.15.0
|
||||
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.3.0
|
||||
github.com/casdoor/oss v1.4.1
|
||||
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
|
||||
@ -23,27 +21,26 @@ require (
|
||||
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
||||
github.com/fogleman/gg v1.3.0
|
||||
github.com/forestmgy/ldapserver v1.1.0
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5
|
||||
github.com/go-git/go-git/v5 v5.6.0
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-mysql-org/go-mysql v1.7.0
|
||||
github.com/go-pay/gopay v1.5.72
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/go-webauthn/webauthn v0.6.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
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/mitchellh/mapstructure v1.5.0
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/nyaruka/phonenumbers v1.1.5
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.11.1
|
||||
github.com/prometheus/client_model v0.3.0
|
||||
github.com/prometheus/client_model v0.4.0
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/gosaml2 v0.9.0
|
||||
@ -62,11 +59,10 @@ require (
|
||||
github.com/xorm-io/core v0.7.4
|
||||
github.com/xorm-io/xorm v1.1.6
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/api v0.138.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.13.0
|
||||
google.golang.org/api v0.150.0
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung aktiviert",
|
||||
"Unauthorized operation": "Nicht autorisierte Operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unbekannter Authentifizierungstyp (nicht Passwort oder Anbieter), Formular = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s und %s stimmen nicht überein"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "E-Mail ist ungültig",
|
||||
"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 is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Organization does not exist": "Organisation existiert nicht",
|
||||
"Password must have at least 6 characters": "Das Passwort muss mindestens 6 Zeichen enthalten",
|
||||
"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",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "El proveedor: %s no está habilitado para la aplicación",
|
||||
"Unauthorized operation": "Operación no autorizada",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Tipo de autenticación desconocido (no es contraseña o proveedor), formulario = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Los servicios %s y %s no coinciden"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "El correo electrónico no es válido",
|
||||
"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 is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Organization does not exist": "La organización no existe",
|
||||
"Password must have at least 6 characters": "La contraseña debe tener al menos 6 caracteres",
|
||||
"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",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
|
||||
"Unauthorized operation": "Opération non autorisée",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "Le tag de l’utilisateur %s n’est pas répertorié dans les tags de l’application"
|
||||
"User's tag: %s is not listed in the application's tags": "Le tag de l’utilisateur %s n’est pas répertorié dans les tags de l’application",
|
||||
"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": {
|
||||
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "L'adresse e-mail est invalide",
|
||||
"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 is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Organization does not exist": "L'organisation n'existe pas",
|
||||
"Password must have at least 6 characters": "Le mot de passe doit comporter au moins 6 caractères",
|
||||
"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",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
|
||||
"Unauthorized operation": "Operasi tidak sah",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Jenis otentikasi tidak diketahui (bukan kata sandi atau pemberi), formulir = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Layanan %s dan %s tidak cocok"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email tidak valid",
|
||||
"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 is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Organization does not exist": "Organisasi tidak ada",
|
||||
"Password must have at least 6 characters": "Kata sandi harus memiliki minimal 6 karakter",
|
||||
"Phone already exists": "Telepon sudah ada",
|
||||
"Phone cannot be empty": "Telepon tidak boleh kosong",
|
||||
"Phone number is invalid": "Nomor telepon tidak valid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "サービス%sと%sは一致しません"
|
||||
@ -33,11 +34,12 @@
|
||||
"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",
|
||||
"LDAP user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
|
||||
"LastName cannot be blank": "姓は空白にできません",
|
||||
"Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください",
|
||||
"Organization does not exist": "組織は存在しません",
|
||||
"Password must have at least 6 characters": "パスワードは少なくとも6つの文字が必要です",
|
||||
"Phone already exists": "電話はすでに存在しています",
|
||||
"Phone cannot be empty": "電話は空っぽにできません",
|
||||
"Phone number is invalid": "電話番号が無効です",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다"
|
||||
@ -33,11 +34,12 @@
|
||||
"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",
|
||||
"LDAP user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
|
||||
"LastName cannot be blank": "성은 비어 있을 수 없습니다",
|
||||
"Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요",
|
||||
"Organization does not exist": "조직은 존재하지 않습니다",
|
||||
"Password must have at least 6 characters": "암호는 적어도 6자 이상이어야 합니다",
|
||||
"Phone already exists": "전화기는 이미 존재합니다",
|
||||
"Phone cannot be empty": "전화는 비워 둘 수 없습니다",
|
||||
"Phone number is invalid": "전화번호가 유효하지 않습니다",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Сервисы %s и %s не совпадают"
|
||||
@ -33,11 +34,12 @@
|
||||
"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",
|
||||
"LDAP user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
|
||||
"LastName cannot be blank": "Фамилия не может быть пустой",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
|
||||
"Organization does not exist": "Организация не существует",
|
||||
"Password must have at least 6 characters": "Пароль должен содержать не менее 6 символов",
|
||||
"Phone already exists": "Телефон уже существует",
|
||||
"Phone cannot be empty": "Телефон не может быть пустым",
|
||||
"Phone number is invalid": "Номер телефона является недействительным",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"The provider: %s is not enabled for the application": "Nhà cung cấp: %s không được kích hoạt cho ứng dụng",
|
||||
"Unauthorized operation": "Hoạt động không được ủy quyền",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Loại xác thực không xác định (không phải mật khẩu hoặc nhà cung cấp), biểu mẫu = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Dịch sang tiếng Việt: Dịch vụ %s và %s không khớp"
|
||||
@ -33,11 +34,12 @@
|
||||
"Email is invalid": "Địa chỉ email không hợp lệ",
|
||||
"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 is invalid": "Invitation code is invalid",
|
||||
"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",
|
||||
"Organization does not exist": "Tổ chức không tồn tại",
|
||||
"Password must have at least 6 characters": "Mật khẩu phải ít nhất 6 ký tự",
|
||||
"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ệ",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"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": "用户的标签: %s不在该应用的标签列表中"
|
||||
"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": {
|
||||
"Service %s and %s do not match": "服务%s与%s不匹配"
|
||||
@ -33,11 +34,12 @@
|
||||
"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",
|
||||
"LDAP user name or password incorrect": "LDAP密码错误",
|
||||
"LastName cannot be blank": "姓不可以为空",
|
||||
"Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid,请检查您的 LDAP 服务器",
|
||||
"Organization does not exist": "组织不存在",
|
||||
"Password must have at least 6 characters": "新密码至少为6位",
|
||||
"Phone already exists": "该手机号已存在",
|
||||
"Phone cannot be empty": "手机号不可为空",
|
||||
"Phone number is invalid": "无效手机号",
|
||||
|
@ -85,10 +85,12 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
payload.Set("client_id", idp.Config.ClientID)
|
||||
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
||||
|
||||
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -97,10 +99,10 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
pToken := &AdfsToken{}
|
||||
err = json.Unmarshal(data, pToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
if pToken.ErrMsg != "" {
|
||||
return nil, fmt.Errorf("pToken.Errmsg = %s", pToken.ErrMsg)
|
||||
return nil, fmt.Errorf(pToken.ErrMsg)
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
|
126
idp/azuread_b2c.go
Normal file
126
idp/azuread_b2c.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type AzureADB2CProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
Tenant string
|
||||
UserFlow string
|
||||
}
|
||||
|
||||
func NewAzureAdB2cProvider(clientId, clientSecret, redirectUrl, tenant string, userFlow string) *AzureADB2CProvider {
|
||||
return &AzureADB2CProvider{
|
||||
Config: &oauth2.Config{
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("https://%s.b2clogin.com/%s.onmicrosoft.com/%s/oauth2/v2.0/authorize", tenant, tenant, userFlow),
|
||||
TokenURL: fmt.Sprintf("https://%s.b2clogin.com/%s.onmicrosoft.com/%s/oauth2/v2.0/token", tenant, tenant, userFlow),
|
||||
},
|
||||
Scopes: []string{"openid", "email"},
|
||||
},
|
||||
Tenant: tenant,
|
||||
UserFlow: userFlow,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AzureADB2CProvider) SetHttpClient(client *http.Client) {
|
||||
p.Client = client
|
||||
}
|
||||
|
||||
type AzureadB2cToken struct {
|
||||
IdToken string `json:"id_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
NotBefore int `json:"not_before"`
|
||||
IdTokenExpiresIn int `json:"id_token_expires_in"`
|
||||
ProfileInfo string `json:"profile_info"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
func (p *AzureADB2CProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
payload := url.Values{}
|
||||
payload.Set("code", code)
|
||||
payload.Set("grant_type", "authorization_code")
|
||||
payload.Set("client_id", p.Config.ClientID)
|
||||
payload.Set("client_secret", p.Config.ClientSecret)
|
||||
payload.Set("redirect_uri", p.Config.RedirectURL)
|
||||
|
||||
resp, err := p.Client.PostForm(p.Config.Endpoint.TokenURL, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pToken := &AzureadB2cToken{}
|
||||
err = json.Unmarshal(data, pToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: pToken.IdToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(pToken.IdTokenExpiresIn), 0),
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (p *AzureADB2CProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
userInfoEndpoint := fmt.Sprintf("https://%s.b2clogin.com/%s.onmicrosoft.com/%s/openid/v2.0/userinfo", p.Tenant, p.Tenant, p.UserFlow)
|
||||
req, err := http.NewRequest("GET", userInfoEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Authorization", "Bearer "+token.AccessToken)
|
||||
|
||||
resp, err := p.Client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("error fetching user info: status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var userInfo UserInfo
|
||||
err = json.Unmarshal(bodyBytes, &userInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
@ -91,6 +91,8 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error
|
||||
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "ADFS":
|
||||
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||
case "AzureADB2C":
|
||||
return NewAzureAdB2cProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl, idpInfo.AppId), nil
|
||||
case "Baidu":
|
||||
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Alipay":
|
||||
|
@ -104,7 +104,9 @@
|
||||
}
|
||||
],
|
||||
"redirectUris": [""],
|
||||
"expireInHours": 168
|
||||
"expireInHours": 168,
|
||||
"failedSigninLimit": 5,
|
||||
"failedSigninfrozenTime": 15
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
|
@ -16,6 +16,7 @@ package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"log"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@ -113,11 +114,22 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
||||
dn := fmt.Sprintf("uid=%s,cn=%s,%s", user.Id, user.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
e.AddAttribute(message.AttributeDescription("uid"), message.AttributeValue(user.Id))
|
||||
e.AddAttribute(message.AttributeDescription("cn"), message.AttributeValue(user.Name))
|
||||
for _, attr := range r.Attributes() {
|
||||
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))
|
||||
@ -128,3 +140,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
func hash(s string) uint32 {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(s))
|
||||
return h.Sum32()
|
||||
}
|
||||
|
186
ldap/util.go
186
ldap/util.go
@ -24,8 +24,72 @@ import (
|
||||
"github.com/lor00x/goldap/message"
|
||||
|
||||
ldap "github.com/forestmgy/ldapserver"
|
||||
|
||||
"github.com/xorm-io/builder"
|
||||
)
|
||||
|
||||
type AttributeMapper func(user *object.User) message.AttributeValue
|
||||
|
||||
type FieldRelation struct {
|
||||
userField string
|
||||
notSearchable bool
|
||||
hideOnStarOp bool
|
||||
fieldMapper AttributeMapper
|
||||
}
|
||||
|
||||
func (rel FieldRelation) GetField() (string, error) {
|
||||
if rel.notSearchable {
|
||||
return "", fmt.Errorf("attribute %s not supported", rel.userField)
|
||||
}
|
||||
return rel.userField, nil
|
||||
}
|
||||
|
||||
func (rel FieldRelation) GetAttributeValue(user *object.User) message.AttributeValue {
|
||||
return rel.fieldMapper(user)
|
||||
}
|
||||
|
||||
var ldapAttributesMapping = map[string]FieldRelation{
|
||||
"cn": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Name)
|
||||
}},
|
||||
"uid": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Name)
|
||||
}},
|
||||
"displayname": {userField: "displayName", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.DisplayName)
|
||||
}},
|
||||
"email": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Email)
|
||||
}},
|
||||
"mail": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Email)
|
||||
}},
|
||||
"mobile": {userField: "phone", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Phone)
|
||||
}},
|
||||
"title": {userField: "tag", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Tag)
|
||||
}},
|
||||
"userPassword": {
|
||||
userField: "userPassword",
|
||||
notSearchable: true,
|
||||
fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(getUserPasswordWithType(user))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var AdditionalLdapAttributes []message.LDAPString
|
||||
|
||||
func init() {
|
||||
for k, v := range ldapAttributesMapping {
|
||||
if v.hideOnStarOp {
|
||||
continue
|
||||
}
|
||||
AdditionalLdapAttributes = append(AdditionalLdapAttributes, message.LDAPString(k))
|
||||
}
|
||||
}
|
||||
|
||||
func getNameAndOrgFromDN(DN string) (string, string, error) {
|
||||
DNFields := strings.Split(DN, ",")
|
||||
params := make(map[string]string, len(DNFields))
|
||||
@ -87,6 +151,92 @@ func stringInSlice(value string, list []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
|
||||
switch f := filter.(type) {
|
||||
case message.FilterAnd:
|
||||
conditions := make([]builder.Cond, len(f))
|
||||
for i, v := range f {
|
||||
cond, err := buildUserFilterCondition(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditions[i] = cond
|
||||
}
|
||||
return builder.And(conditions...), nil
|
||||
case message.FilterOr:
|
||||
conditions := make([]builder.Cond, len(f))
|
||||
for i, v := range f {
|
||||
cond, err := buildUserFilterCondition(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditions[i] = cond
|
||||
}
|
||||
return builder.Or(conditions...), nil
|
||||
case message.FilterNot:
|
||||
cond, err := buildUserFilterCondition(f.Filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Not{cond}, nil
|
||||
case message.FilterEqualityMatch:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Eq{field: string(f.AssertionValue())}, nil
|
||||
case message.FilterPresent:
|
||||
field, err := getUserFieldFromAttribute(string(f))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.NotNull{field}, nil
|
||||
case message.FilterGreaterOrEqual:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Gte{field: string(f.AssertionValue())}, nil
|
||||
case message.FilterLessOrEqual:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Lte{field: string(f.AssertionValue())}, nil
|
||||
case message.FilterSubstrings:
|
||||
field, err := getUserFieldFromAttribute(string(f.Type_()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var expr string
|
||||
for _, substring := range f.Substrings() {
|
||||
switch s := substring.(type) {
|
||||
case message.SubstringInitial:
|
||||
expr += string(s) + "%"
|
||||
continue
|
||||
case message.SubstringAny:
|
||||
expr += string(s) + "%"
|
||||
continue
|
||||
case message.SubstringFinal:
|
||||
expr += string(s)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return builder.Expr(field+" LIKE ?", expr), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("LDAP filter operation %#v not supported", f)
|
||||
}
|
||||
}
|
||||
|
||||
func buildSafeCondition(filter interface{}) builder.Cond {
|
||||
condition, err := buildUserFilterCondition(filter)
|
||||
if err != nil {
|
||||
log.Printf("err = %v", err.Error())
|
||||
return nil
|
||||
}
|
||||
return condition
|
||||
}
|
||||
|
||||
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
|
||||
var err error
|
||||
r := m.GetSearchRequest()
|
||||
@ -98,15 +248,14 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
|
||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||
if m.Client.IsGlobalAdmin && org == "*" {
|
||||
|
||||
filteredUsers, err = object.GetGlobalUsers()
|
||||
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
|
||||
filteredUsers, err = object.GetUsers(org)
|
||||
filteredUsers, err = object.GetUsersWithFilter(org, buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -148,7 +297,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
return nil, ldap.LDAPResultNoSuchObject
|
||||
}
|
||||
|
||||
users, err := object.GetUsersByTag(org, name)
|
||||
users, err := object.GetUsersByTagWithFilter(org, name, buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -182,24 +331,17 @@ func getUserPasswordWithType(user *object.User) string {
|
||||
}
|
||||
|
||||
func getAttribute(attributeName string, user *object.User) message.AttributeValue {
|
||||
switch attributeName {
|
||||
case "cn":
|
||||
return message.AttributeValue(user.Name)
|
||||
case "uid":
|
||||
return message.AttributeValue(user.Name)
|
||||
case "displayname":
|
||||
return message.AttributeValue(user.DisplayName)
|
||||
case "email":
|
||||
return message.AttributeValue(user.Email)
|
||||
case "mail":
|
||||
return message.AttributeValue(user.Email)
|
||||
case "mobile":
|
||||
return message.AttributeValue(user.Phone)
|
||||
case "title":
|
||||
return message.AttributeValue(user.Tag)
|
||||
case "userPassword":
|
||||
return message.AttributeValue(getUserPasswordWithType(user))
|
||||
default:
|
||||
v, ok := ldapAttributesMapping[attributeName]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return v.GetAttributeValue(user)
|
||||
}
|
||||
|
||||
func getUserFieldFromAttribute(attributeName string) (string, error) {
|
||||
v, ok := ldapAttributesMapping[attributeName]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("attribute %s not supported", attributeName)
|
||||
}
|
||||
return v.GetField()
|
||||
}
|
||||
|
87
ldap/util_test.go
Normal file
87
ldap/util_test.go
Normal file
@ -0,0 +1,87 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
ber "github.com/go-asn1-ber/asn1-ber"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/lor00x/goldap/message"
|
||||
"github.com/xorm-io/builder"
|
||||
)
|
||||
|
||||
func args(exp ...interface{}) []interface{} {
|
||||
return exp
|
||||
}
|
||||
|
||||
func TestLdapFilterAsQuery(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input string
|
||||
expectedExpr string
|
||||
expectedArgs []interface{}
|
||||
}{
|
||||
{"Should be SQL for FilterAnd", "(&(mail=2)(email=1))", "email=? AND email=?", args("2", "1")},
|
||||
{"Should be SQL for FilterOr", "(|(mail=2)(email=1))", "email=? OR email=?", args("2", "1")},
|
||||
{"Should be SQL for FilterNot", "(!(mail=2))", "NOT email=?", args("2")},
|
||||
{"Should be SQL for FilterEqualityMatch", "(mail=2)", "email=?", args("2")},
|
||||
{"Should be SQL for FilterPresent", "(mail=*)", "email IS NOT NULL", nil},
|
||||
{"Should be SQL for FilterGreaterOrEqual", "(mail>=admin)", "email>=?", args("admin")},
|
||||
{"Should be SQL for FilterLessOrEqual", "(mail<=admin)", "email<=?", args("admin")},
|
||||
{"Should be SQL for FilterSubstrings", "(mail=admin*ex*c*m)", "email LIKE ?", args("admin%ex%c%m")},
|
||||
}
|
||||
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
searchRequest, err := buildLdapSearchRequest(scenery.input)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Unable to create searchRequest", err)
|
||||
}
|
||||
m, err := message.ReadLDAPMessage(message.NewBytes(0, searchRequest.Bytes()))
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Unable to create searchRequest", err)
|
||||
}
|
||||
req := m.ProtocolOp().(message.SearchRequest)
|
||||
|
||||
cond, err := buildUserFilterCondition(req.Filter())
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Unable to build condition", err)
|
||||
}
|
||||
expr, args, err := builder.ToSQL(cond)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Unable to build sql", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, scenery.expectedExpr, expr)
|
||||
assert.Equal(t, scenery.expectedArgs, args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func buildLdapSearchRequest(filter string) (*ber.Packet, error) {
|
||||
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
|
||||
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 1, "MessageID"))
|
||||
|
||||
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, goldap.ApplicationSearchRequest, nil, "Search Request")
|
||||
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Base DN"))
|
||||
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, 0, "Scope"))
|
||||
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, 0, "Deref Aliases"))
|
||||
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 0, "Size Limit"))
|
||||
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 0, "Time Limit"))
|
||||
pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, false, "Types Only"))
|
||||
// compile and encode filter
|
||||
filterPacket, err := goldap.CompileFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkt.AppendChild(filterPacket)
|
||||
// encode attributes
|
||||
attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
|
||||
attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "*", "Attribute"))
|
||||
pkt.AppendChild(attributesPacket)
|
||||
|
||||
packet.AppendChild(pkt)
|
||||
|
||||
return packet, nil
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
name: casdoor
|
||||
name: casdoor-helm-charts
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
@ -15,10 +15,10 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
version: 0.3.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
||||
appVersion: "1.18.0"
|
||||
|
@ -59,6 +59,9 @@ spec:
|
||||
volumeMounts:
|
||||
- name: config-volume
|
||||
mountPath: /conf
|
||||
{{ if .Values.extraContainersEnabled }}
|
||||
{{- .Values.extraContainers | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: config-volume
|
||||
projected:
|
||||
|
@ -108,3 +108,10 @@ nodeSelector: {}
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
# -- Optionally add extra sidecar containers.
|
||||
extraContainersEnabled: false
|
||||
extraContainers: ""
|
||||
# extraContainers: |
|
||||
# - name: ...
|
||||
# image: ...
|
@ -15,10 +15,11 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
@ -39,26 +40,29 @@ func NewCustomHttpProvider(endpoint string, method string, paramName string) (*H
|
||||
}
|
||||
|
||||
func (c *HttpNotificationClient) Send(ctx context.Context, subject string, content string) error {
|
||||
var req *http.Request
|
||||
var err error
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
|
||||
req, err := http.NewRequest(c.method, c.endpoint, bytes.NewBufferString(content))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.method == "POST" {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.PostForm = map[string][]string{
|
||||
c.paramName: {content},
|
||||
formValues := url.Values{}
|
||||
formValues.Set(c.paramName, content)
|
||||
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else if c.method == "GET" {
|
||||
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add(c.paramName, content)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -66,7 +70,7 @@ func (c *HttpNotificationClient) Send(ctx context.Context, subject string, conte
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("SendMessage() error, custom HTTP Notification request failed with status: %s", resp.Status)
|
||||
return fmt.Errorf("HttpNotificationClient's SendMessage() error, custom HTTP Notification request failed with status: %s", resp.Status)
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -90,6 +90,9 @@ type Application struct {
|
||||
FormOffset int `json:"formOffset"`
|
||||
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
|
||||
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
|
||||
|
||||
FailedSigninLimit int `json:"failedSigninLimit"`
|
||||
FailedSigninfrozenTime int `json:"failedSigninfrozenTime"`
|
||||
}
|
||||
|
||||
func GetApplicationCount(owner, field, value string) (int64, error) {
|
||||
|
@ -28,8 +28,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SigninWrongTimesLimit = 5
|
||||
LastSignWrongTimeDuration = time.Minute * 15
|
||||
DefaultFailedSigninLimit = 5
|
||||
// DefaultFailedSigninfrozenTime The unit of frozen time is minutes
|
||||
DefaultFailedSigninfrozenTime = 15
|
||||
)
|
||||
|
||||
func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string {
|
||||
@ -143,10 +144,15 @@ func CheckUserSignup(application *Application, organization *Organization, form
|
||||
}
|
||||
|
||||
func checkSigninErrorTimes(user *User, lang string) error {
|
||||
if user.SigninWrongTimes >= SigninWrongTimesLimit {
|
||||
failedSigninLimit, failedSigninfrozenTime, err := GetFailedSigninConfigByUser(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user.SigninWrongTimes >= failedSigninLimit {
|
||||
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
|
||||
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
|
||||
minutes := int(LastSignWrongTimeDuration.Minutes() - 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 {
|
||||
@ -237,22 +243,28 @@ func checkLdapUserPassword(user *User, password string, lang string) error {
|
||||
|
||||
searchResult, err := conn.Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
if len(searchResult.Entries) > 1 {
|
||||
conn.Close()
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
|
||||
}
|
||||
|
||||
hit = true
|
||||
dn := searchResult.Entries[0].DN
|
||||
if err := conn.Conn.Bind(dn, password); err == nil {
|
||||
if err = conn.Conn.Bind(dn, password); err == nil {
|
||||
ldapLoginSuccess = true
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
if !ldapLoginSuccess {
|
||||
@ -368,7 +380,7 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
||||
allowCount := 0
|
||||
denyCount := 0
|
||||
for _, permission := range permissions {
|
||||
if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
||||
if !permission.IsEnabled || permission.State != "Approved" || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -473,7 +485,14 @@ func CheckToEnableCaptcha(application *Application, organization, username strin
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return user != nil && user.SigninWrongTimes >= SigninWrongTimesLimit, nil
|
||||
|
||||
var failedSigninLimit int
|
||||
if application.FailedSigninLimit == 0 {
|
||||
failedSigninLimit = 5
|
||||
} else {
|
||||
failedSigninLimit = application.FailedSigninLimit
|
||||
}
|
||||
return user != nil && user.SigninWrongTimes >= failedSigninLimit, nil
|
||||
}
|
||||
return providerItem.Rule == "Always", nil
|
||||
}
|
||||
|
@ -47,18 +47,42 @@ func resetUserSigninErrorTimes(user *User) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func GetFailedSigninConfigByUser(user *User) (int, int, error) {
|
||||
application, err := GetApplicationByUser(user)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
failedSigninLimit := application.FailedSigninLimit
|
||||
failedSigninfrozenTime := application.FailedSigninfrozenTime
|
||||
|
||||
// 0 as an initialization value, corresponding to the default configuration parameters
|
||||
if failedSigninLimit == 0 {
|
||||
failedSigninLimit = DefaultFailedSigninLimit
|
||||
}
|
||||
if failedSigninfrozenTime == 0 {
|
||||
failedSigninfrozenTime = DefaultFailedSigninfrozenTime
|
||||
}
|
||||
|
||||
return failedSigninLimit, failedSigninfrozenTime, nil
|
||||
}
|
||||
|
||||
func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
|
||||
failedSigninLimit, failedSigninfrozenTime, errSignin := GetFailedSigninConfigByUser(user)
|
||||
if errSignin != nil {
|
||||
return errSignin
|
||||
}
|
||||
|
||||
// increase failed login count
|
||||
if user.SigninWrongTimes < SigninWrongTimesLimit {
|
||||
if user.SigninWrongTimes < failedSigninLimit {
|
||||
user.SigninWrongTimes++
|
||||
}
|
||||
|
||||
if user.SigninWrongTimes >= SigninWrongTimesLimit {
|
||||
if user.SigninWrongTimes >= failedSigninLimit {
|
||||
// record the latest failed login time
|
||||
user.LastSigninWrongTime = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
@ -69,7 +93,7 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
|
||||
leftChances := failedSigninLimit - user.SigninWrongTimes
|
||||
if leftChances == 0 && enableCaptcha {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect"))
|
||||
} else if leftChances >= 0 {
|
||||
@ -77,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"), int(LastSignWrongTimeDuration.Minutes()))
|
||||
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)
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
|
||||
}
|
||||
|
||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl)
|
||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl, provider.Endpoint, provider.Method)
|
||||
|
||||
fromAddress := provider.ClientId2
|
||||
if fromAddress == "" {
|
||||
|
@ -125,6 +125,10 @@ func (enforcer *Enforcer) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", enforcer.Owner, enforcer.Name)
|
||||
}
|
||||
|
||||
func (enforcer *Enforcer) GetModelAndAdapter() string {
|
||||
return util.GetId(enforcer.Model, enforcer.Adapter)
|
||||
}
|
||||
|
||||
func (enforcer *Enforcer) InitEnforcer() error {
|
||||
if enforcer.Enforcer != nil {
|
||||
return nil
|
||||
|
@ -271,7 +271,9 @@ func GetGroupUsers(groupId string) ([]*User, error) {
|
||||
users := []*User{}
|
||||
owner, _ := util.GetOwnerAndNameFromId(groupId)
|
||||
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ormer.Engine.Where("owner = ?", owner).In("name", names).Find(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -303,6 +305,9 @@ func GroupChangeTrigger(oldName, newName string) error {
|
||||
|
||||
groups := []*Group{}
|
||||
err = session.Where("parent_id = ?", oldName).Find(&groups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, group := range groups {
|
||||
group.ParentId = newName
|
||||
_, err := session.ID(core.PK{group.Owner, group.Name}).Cols("parent_id").Update(group)
|
||||
|
@ -396,15 +396,22 @@ func initBuiltInPermission() {
|
||||
Name: "permission-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Built-in Permission",
|
||||
Description: "Built-in Permission",
|
||||
Users: []string{"built-in/*"},
|
||||
Groups: []string{},
|
||||
Roles: []string{},
|
||||
Domains: []string{},
|
||||
Model: "model-built-in",
|
||||
Adapter: "",
|
||||
ResourceType: "Application",
|
||||
Resources: []string{"app-built-in"},
|
||||
Actions: []string{"Read", "Write", "Admin"},
|
||||
Effect: "Allow",
|
||||
IsEnabled: true,
|
||||
Submitter: "admin",
|
||||
Approver: "admin",
|
||||
ApproveTime: util.GetCurrentTime(),
|
||||
State: "Approved",
|
||||
}
|
||||
_, err = AddPermission(permission)
|
||||
if err != nil {
|
||||
|
@ -100,6 +100,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
|
||||
|
||||
users, err := conn.GetLdapUsers(ldap)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||
continue
|
||||
}
|
||||
@ -111,6 +112,8 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
|
||||
} else {
|
||||
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,6 +81,17 @@ func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
|
||||
return &LdapConn{Conn: conn, IsAD: isAD}, nil
|
||||
}
|
||||
|
||||
func (l *LdapConn) Close() {
|
||||
if l.Conn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := l.Conn.Unbind()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
SearchFilter := "(objectClass=*)"
|
||||
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||
|
104
object/ormer.go
104
object/ormer.go
@ -234,12 +234,32 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Group))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(User))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Group))
|
||||
err = a.Engine.Sync2(new(Application))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Provider))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Resource))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Cert))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -269,17 +289,7 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Provider))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Application))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Resource))
|
||||
err = a.Engine.Sync2(new(Session))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -289,26 +299,6 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(VerificationRecord))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Webhook))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Syncer))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Cert))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Product))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -319,6 +309,36 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Plan))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Pricing))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Subscription))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Syncer))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Webhook))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(VerificationRecord))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Ldap))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -333,24 +353,4 @@ func (a *Ormer) createTable() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Session))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Subscription))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Plan))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Pricing))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,11 @@ func checkPermissionValid(permission *Permission) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
@ -442,9 +446,8 @@ func GetMaskedPermissions(permissions []*Permission) []*Permission {
|
||||
// as the policyFilter when the enforcer load policy).
|
||||
func GroupPermissionsByModelAdapter(permissions []*Permission) map[string][]string {
|
||||
m := make(map[string][]string)
|
||||
|
||||
for _, permission := range permissions {
|
||||
key := permission.Model + permission.Adapter
|
||||
key := permission.GetModelAndAdapter()
|
||||
permissionIds, ok := m[key]
|
||||
if !ok {
|
||||
m[key] = []string{permission.GetId()}
|
||||
@ -460,9 +463,17 @@ func (p *Permission) GetId() string {
|
||||
return util.GetId(p.Owner, p.Name)
|
||||
}
|
||||
|
||||
func (p *Permission) GetModelAndAdapter() string {
|
||||
return util.GetId(p.Model, p.Adapter)
|
||||
}
|
||||
|
||||
func (p *Permission) isUserHit(name string) bool {
|
||||
targetOrg, targetName := util.GetOwnerAndNameFromId(name)
|
||||
for _, user := range p.Users {
|
||||
if user == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
userOrg, userName := util.GetOwnerAndNameFromId(user)
|
||||
if userOrg == targetOrg && (userName == "*" || userName == targetName) {
|
||||
return true
|
||||
@ -476,9 +487,14 @@ func (p *Permission) isRoleHit(userId string) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range p.Roles {
|
||||
if role == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, targetRole := range targetRoles {
|
||||
if targetRole.GetId() == role {
|
||||
if role == targetRole.GetId() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/casbin/casbin/v2/log"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
)
|
||||
|
||||
@ -137,6 +138,16 @@ func getPolicies(permission *Permission) [][]string {
|
||||
}
|
||||
|
||||
func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error) {
|
||||
roleOwner, roleName := util.GetOwnerAndNameFromId(roleId)
|
||||
if roleName == "*" {
|
||||
roles, err := GetRoles(roleOwner)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
role, err := GetRole(roleId)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
@ -162,7 +173,7 @@ func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error)
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func getGroupingPolicies(permission *Permission) [][]string {
|
||||
func getGroupingPolicies(permission *Permission) ([][]string, error) {
|
||||
var groupingPolicies [][]string
|
||||
|
||||
domainExist := len(permission.Domains) > 0
|
||||
@ -170,12 +181,18 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
|
||||
for _, roleId := range permission.Roles {
|
||||
visited := map[string]struct{}{}
|
||||
|
||||
if roleId == "*" {
|
||||
roleId = util.GetId(permission.Owner, "*")
|
||||
}
|
||||
|
||||
rolesInRole, err := getRolesInRole(roleId, visited)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, role := range rolesInRole {
|
||||
roleId := role.GetId()
|
||||
roleId = role.GetId()
|
||||
for _, subUser := range role.Users {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
@ -198,7 +215,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
}
|
||||
}
|
||||
|
||||
return groupingPolicies
|
||||
return groupingPolicies, nil
|
||||
}
|
||||
|
||||
func addPolicies(permission *Permission) error {
|
||||
@ -231,7 +248,10 @@ func addGroupingPolicies(permission *Permission) error {
|
||||
return err
|
||||
}
|
||||
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
@ -249,7 +269,10 @@ func removeGroupingPolicies(permission *Permission) error {
|
||||
return err
|
||||
}
|
||||
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||
@ -261,24 +284,28 @@ func removeGroupingPolicies(permission *Permission) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type CasbinRequest = []interface{}
|
||||
|
||||
func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
|
||||
func Enforce(permission *Permission, request []string, permissionIds ...string) (bool, error) {
|
||||
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return enforcer.Enforce(*request...)
|
||||
// type transformation
|
||||
interfaceRequest := util.StringToInterfaceArray(request)
|
||||
|
||||
return enforcer.Enforce(interfaceRequest...)
|
||||
}
|
||||
|
||||
func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
|
||||
func BatchEnforce(permission *Permission, requests [][]string, permissionIds ...string) ([]bool, error) {
|
||||
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return enforcer.BatchEnforce(*requests)
|
||||
// type transformation
|
||||
interfaceRequests := util.StringToInterfaceArray2d(requests)
|
||||
|
||||
return enforcer.BatchEnforce(interfaceRequests)
|
||||
}
|
||||
|
||||
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([]string, error) {
|
||||
@ -287,7 +314,12 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, role := range GetAllRoles(userId) {
|
||||
allRoles, err := GetAllRoles(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, role := range allRoles {
|
||||
permissionsByRole, err := GetPermissionsByRole(role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -321,17 +353,17 @@ func GetAllActions(userId string) ([]string, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func GetAllRoles(userId string) []string {
|
||||
func GetAllRoles(userId string) ([]string, error) {
|
||||
roles, err := getRolesByUser(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res []string
|
||||
res := []string{}
|
||||
for _, role := range roles {
|
||||
res = append(res, role.Name)
|
||||
}
|
||||
return res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func GetBuiltInModel(modelText string) (model.Model, error) {
|
||||
|
@ -37,7 +37,7 @@ type Provider struct {
|
||||
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ClientId string `xorm:"varchar(200)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
||||
ClientSecret string `xorm:"varchar(3000)" json:"clientSecret"`
|
||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||
ClientSecret2 string `xorm:"varchar(500)" json:"clientSecret2"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
@ -52,7 +52,7 @@ type Provider struct {
|
||||
Port int `json:"port"`
|
||||
DisableSsl bool `json:"disableSsl"` // If the provider type is WeChat, DisableSsl means EnableQRCode
|
||||
Title string `xorm:"varchar(100)" json:"title"`
|
||||
Content string `xorm:"varchar(1000)" json:"content"` // If provider type is WeChat, Content means QRCode string by Base64 encoding
|
||||
Content string `xorm:"varchar(2000)" json:"content"` // If provider type is WeChat, Content means QRCode string by Base64 encoding
|
||||
Receiver string `xorm:"varchar(100)" json:"receiver"`
|
||||
|
||||
RegionId string `xorm:"varchar(100)" json:"regionId"`
|
||||
@ -417,7 +417,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
|
||||
providerInfo.ClientId = provider.ClientId2
|
||||
providerInfo.ClientSecret = provider.ClientSecret2
|
||||
}
|
||||
} else if provider.Type == "AzureAD" || provider.Type == "ADFS" || provider.Type == "Okta" {
|
||||
} else if provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "ADFS" || provider.Type == "Okta" {
|
||||
providerInfo.HostUrl = provider.Domain
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
)
|
||||
|
||||
// https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/sec_usr_radatt/configuration/xe-16/sec-usr-radatt-xe-16-book/sec-rad-ov-ietf-attr.html
|
||||
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a
|
||||
type RadiusAccounting struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
|
@ -271,6 +271,9 @@ func getRolesByUserInternal(userId string) ([]*Role, error) {
|
||||
if err != nil {
|
||||
return roles, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, fmt.Errorf("The user: %s doesn't exist", userId)
|
||||
}
|
||||
|
||||
query := ormer.Engine.Alias("r").Where("r.users like ?", fmt.Sprintf("%%%s%%", userId))
|
||||
for _, group := range user.Groups {
|
||||
|
@ -28,10 +28,10 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/RobotsAndPencils/go-saml"
|
||||
"github.com/beevik/etree"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
saml "github.com/russellhaering/gosaml2"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
|
||||
@ -283,15 +283,15 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
}
|
||||
}
|
||||
|
||||
var authnRequest saml.AuthnRequest
|
||||
var authnRequest saml.AuthNRequest
|
||||
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
|
||||
if err != nil {
|
||||
return "", "", method, fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request. %s", err.Error())
|
||||
}
|
||||
|
||||
// verify samlRequest
|
||||
if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid {
|
||||
return "", "", method, fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url)
|
||||
if isValid := application.IsRedirectUriValid(authnRequest.Issuer); !isValid {
|
||||
return "", "", method, fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer)
|
||||
}
|
||||
|
||||
// get certificate string
|
||||
@ -317,7 +317,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
// build signedResponse
|
||||
samlResponse, _ := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
||||
samlResponse, _ := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer, authnRequest.ID, application.RedirectUris)
|
||||
randomKeyStore := &X509Key{
|
||||
PrivateKey: cert.PrivateKey,
|
||||
X509Certificate: certificate,
|
||||
|
@ -26,6 +26,8 @@ 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)
|
||||
} else {
|
||||
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
||||
}
|
||||
|
83
object/sms_custom_http.go
Normal file
83
object/sms_custom_http.go
Normal file
@ -0,0 +1,83 @@
|
||||
// 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
type HttpSmsClient struct {
|
||||
endpoint string
|
||||
method string
|
||||
paramName string
|
||||
}
|
||||
|
||||
func newHttpSmsClient(endpoint string, method string, paramName string) (*HttpSmsClient, error) {
|
||||
client := &HttpSmsClient{
|
||||
endpoint: endpoint,
|
||||
method: method,
|
||||
paramName: paramName,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *HttpSmsClient) SendMessage(param map[string]string, targetPhoneNumber ...string) error {
|
||||
phoneNumber := targetPhoneNumber[0]
|
||||
content := param["code"]
|
||||
|
||||
var req *http.Request
|
||||
var err error
|
||||
if c.method == "POST" {
|
||||
formValues := url.Values{}
|
||||
formValues.Set("phoneNumber", phoneNumber)
|
||||
formValues.Set(c.paramName, content)
|
||||
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else if c.method == "GET" {
|
||||
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("phoneNumber", phoneNumber)
|
||||
q.Add(c.paramName, content)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
} else {
|
||||
return fmt.Errorf("HttpSmsClient's SendMessage() error, unsupported method: %s", c.method)
|
||||
}
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("HttpSmsClient's SendMessage() error, custom HTTP SMS request failed with status: %s", resp.Status)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -72,6 +72,10 @@ func GetTruncatedPath(provider *Provider, fullFilePath string, limit int) string
|
||||
}
|
||||
|
||||
func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
|
||||
if provider.Domain != "" && !strings.HasPrefix(provider.Domain, "http://") && !strings.HasPrefix(provider.Domain, "https://") {
|
||||
provider.Domain = fmt.Sprintf("https://%s", provider.Domain)
|
||||
}
|
||||
|
||||
escapedPath := util.UrlJoin(provider.PathPrefix, fullFilePath)
|
||||
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
||||
|
||||
@ -79,9 +83,6 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
||||
if provider.Type != "Local File System" {
|
||||
// provider.Domain = "https://cdn.casbin.com/casdoor/"
|
||||
host = util.GetUrlHost(provider.Domain)
|
||||
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
|
||||
host = fmt.Sprintf("https://%s", host)
|
||||
}
|
||||
} else {
|
||||
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
||||
host = util.UrlJoin(provider.Domain, "/files")
|
||||
@ -90,9 +91,12 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
||||
host = util.UrlJoin(host, provider.Bucket)
|
||||
}
|
||||
|
||||
fileUrl := util.UrlJoin(host, escapePath(objectKey))
|
||||
fileUrl := ""
|
||||
if host != "" {
|
||||
fileUrl = util.UrlJoin(host, escapePath(objectKey))
|
||||
}
|
||||
|
||||
if hasTimestamp {
|
||||
if fileUrl != "" && hasTimestamp {
|
||||
fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
|
||||
}
|
||||
|
||||
@ -112,7 +116,10 @@ func getStorageProvider(provider *Provider, lang string) (oss.StorageInterface,
|
||||
|
||||
if provider.Domain == "" {
|
||||
provider.Domain = storageProvider.GetEndpoint()
|
||||
UpdateProvider(provider.GetId(), provider)
|
||||
_, err := UpdateProvider(provider.GetId(), provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return storageProvider, nil
|
||||
@ -126,7 +133,12 @@ func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
|
||||
|
||||
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
||||
|
||||
_, err = storageProvider.Put(objectKey, fileBuffer)
|
||||
objectKeyRefined := objectKey
|
||||
if provider.Type == "Google Cloud Storage" {
|
||||
objectKeyRefined = strings.TrimPrefix(objectKeyRefined, "/")
|
||||
}
|
||||
|
||||
_, err = storageProvider.Put(objectKeyRefined, fileBuffer)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
132
object/token.go
132
object/token.go
@ -17,6 +17,7 @@ package object
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -51,15 +52,17 @@ type Token struct {
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
|
||||
Code string `xorm:"varchar(100) index" json:"code"`
|
||||
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||
ExpiresIn int `json:"expiresIn"`
|
||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
||||
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
||||
CodeIsUsed bool `json:"codeIsUsed"`
|
||||
CodeExpireIn int64 `json:"codeExpireIn"`
|
||||
Code string `xorm:"varchar(100) index" json:"code"`
|
||||
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||
AccessTokenHash string `xorm:"varchar(100) index" json:"accessTokenHash"`
|
||||
RefreshTokenHash string `xorm:"varchar(100) index" json:"refreshTokenHash"`
|
||||
ExpiresIn int `json:"expiresIn"`
|
||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
||||
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
||||
CodeIsUsed bool `json:"codeIsUsed"`
|
||||
CodeExpireIn int64 `json:"codeExpireIn"`
|
||||
}
|
||||
|
||||
type TokenWrapper struct {
|
||||
@ -141,6 +144,48 @@ func getTokenByCode(code string) (*Token, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func GetTokenByAccessToken(accessToken string) (*Token, error) {
|
||||
token := Token{AccessTokenHash: getTokenHash(accessToken)}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
token = Token{AccessToken: accessToken}
|
||||
existed, err = ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil, nil
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
|
||||
token := Token{RefreshTokenHash: getTokenHash(refreshToken)}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
token = Token{RefreshToken: refreshToken}
|
||||
existed, err = ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil, nil
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func updateUsedByCode(token *Token) bool {
|
||||
affected, err := ormer.Engine.Where("code=?", token.Code).Cols("code_is_used").Update(token)
|
||||
if err != nil {
|
||||
@ -159,6 +204,24 @@ func (token *Token) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", token.Owner, token.Name)
|
||||
}
|
||||
|
||||
func getTokenHash(input string) string {
|
||||
hash := sha256.Sum256([]byte(input))
|
||||
res := hex.EncodeToString(hash[:])
|
||||
if len(res) > 64 {
|
||||
return res[:64]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (token *Token) popularHashes() {
|
||||
if token.AccessTokenHash == "" && token.AccessToken != "" {
|
||||
token.AccessTokenHash = getTokenHash(token.AccessToken)
|
||||
}
|
||||
if token.RefreshTokenHash == "" && token.RefreshToken != "" {
|
||||
token.RefreshTokenHash = getTokenHash(token.RefreshToken)
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateToken(id string, token *Token) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if t, err := getToken(owner, name); err != nil {
|
||||
@ -167,6 +230,8 @@ func UpdateToken(id string, token *Token) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
token.popularHashes()
|
||||
|
||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(token)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -176,6 +241,8 @@ func UpdateToken(id string, token *Token) (bool, error) {
|
||||
}
|
||||
|
||||
func AddToken(token *Token) (bool, error) {
|
||||
token.popularHashes()
|
||||
|
||||
affected, err := ormer.Engine.Insert(token)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -194,18 +261,16 @@ func DeleteToken(token *Token) (bool, error) {
|
||||
}
|
||||
|
||||
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
||||
token := Token{AccessToken: accessToken}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
token, err := GetTokenByAccessToken(accessToken)
|
||||
if err != nil {
|
||||
return false, nil, nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
if token == nil {
|
||||
return false, nil, nil, nil
|
||||
}
|
||||
|
||||
token.ExpiresIn = 0
|
||||
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(&token)
|
||||
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(token)
|
||||
if err != nil {
|
||||
return false, nil, nil, err
|
||||
}
|
||||
@ -215,22 +280,7 @@ func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, e
|
||||
return false, nil, nil, err
|
||||
}
|
||||
|
||||
return affected != 0, application, &token, nil
|
||||
}
|
||||
|
||||
func GetTokenByAccessToken(accessToken string) (*Token, error) {
|
||||
// Check if the accessToken is in the database
|
||||
token := Token{AccessToken: accessToken}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
return affected != 0, application, token, nil
|
||||
}
|
||||
|
||||
func GetTokenByTokenAndApplication(token string, application string) (*Token, error) {
|
||||
@ -432,16 +482,17 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
ErrorDescription: "client_id is invalid",
|
||||
}, nil
|
||||
}
|
||||
|
||||
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||
return &TokenError{
|
||||
Error: InvalidClient,
|
||||
ErrorDescription: "client_secret is invalid",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// check whether the refresh token is valid, and has not expired.
|
||||
token := Token{RefreshToken: refreshToken}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
if err != nil || !existed {
|
||||
token, err := GetTokenByRefreshToken(refreshToken)
|
||||
if err != nil || token == nil {
|
||||
return &TokenError{
|
||||
Error: InvalidGrant,
|
||||
ErrorDescription: "refresh token is invalid, expired or revoked",
|
||||
@ -452,6 +503,12 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cert == nil {
|
||||
return &TokenError{
|
||||
Error: InvalidGrant,
|
||||
ErrorDescription: fmt.Sprintf("cert: %s cannot be found", application.Cert),
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err = ParseJwtToken(refreshToken, cert)
|
||||
if err != nil {
|
||||
@ -460,6 +517,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generate a new token
|
||||
user, err := getUser(application.Organization, token.User)
|
||||
if err != nil {
|
||||
@ -477,6 +535,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
||||
if err != nil {
|
||||
return &TokenError{
|
||||
@ -504,7 +563,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = DeleteToken(&token)
|
||||
_, err = DeleteToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -517,7 +576,6 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
ExpiresIn: newToken.ExpiresIn,
|
||||
Scope: newToken.Scope,
|
||||
}
|
||||
|
||||
return tokenWrapper, nil
|
||||
}
|
||||
|
||||
@ -729,13 +787,13 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
||||
|
||||
// GetTokenByUser
|
||||
// Implicit flow
|
||||
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
|
||||
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
|
||||
err := ExtendUserWithRolesAndPermissions(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/xorm-io/builder"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
@ -116,6 +117,7 @@ type User struct {
|
||||
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||
AzureADB2c string `xorm:"azureadb2c varchar(100)" json:"azureadb2c"`
|
||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
||||
@ -231,6 +233,20 @@ func GetGlobalUsers() ([]*User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetGlobalUsersWithFilter(cond builder.Cond) ([]*User, error) {
|
||||
users := []*User{}
|
||||
session := ormer.Engine.Desc("created_time")
|
||||
if cond != nil {
|
||||
session = session.Where(cond)
|
||||
}
|
||||
err := session.Find(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
|
||||
users := []*User{}
|
||||
session := GetSessionForUser("", offset, limit, field, value, sortField, sortOrder)
|
||||
@ -266,9 +282,27 @@ func GetUsers(owner string) ([]*User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetUsersByTag(owner string, tag string) ([]*User, error) {
|
||||
func GetUsersWithFilter(owner string, cond builder.Cond) ([]*User, error) {
|
||||
users := []*User{}
|
||||
err := ormer.Engine.Desc("created_time").Find(&users, &User{Owner: owner, Tag: tag})
|
||||
session := ormer.Engine.Desc("created_time")
|
||||
if cond != nil {
|
||||
session = session.Where(cond)
|
||||
}
|
||||
err := session.Find(&users, &User{Owner: owner})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetUsersByTagWithFilter(owner string, tag string, cond builder.Cond) ([]*User, error) {
|
||||
users := []*User{}
|
||||
session := ormer.Engine.Desc("created_time")
|
||||
if cond != nil {
|
||||
session = session.Where(cond)
|
||||
}
|
||||
err := session.Find(&users, &User{Owner: owner, Tag: tag})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -589,7 +623,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
"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",
|
||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
||||
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
||||
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
||||
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
||||
@ -852,6 +886,18 @@ func (user *User) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||
}
|
||||
|
||||
func (user *User) GetFriendlyName() string {
|
||||
if user.FirstName != "" && user.LastName != "" {
|
||||
return fmt.Sprintf("%s, %s", user.FirstName, user.LastName)
|
||||
} else if user.DisplayName != "" {
|
||||
return user.DisplayName
|
||||
} else if user.Name != "" {
|
||||
return user.Name
|
||||
} else {
|
||||
return user.Id
|
||||
}
|
||||
}
|
||||
|
||||
func isUserIdGlobalAdmin(userId string) bool {
|
||||
return strings.HasPrefix(userId, "built-in/") || strings.HasPrefix(userId, "app/")
|
||||
}
|
||||
@ -988,7 +1034,10 @@ func GenerateIdForNewUser(application *Application) (string, error) {
|
||||
|
||||
lastUserId := -1
|
||||
if lastUser != nil {
|
||||
lastUserId = util.ParseInt(lastUser.Id)
|
||||
lastUserId, err = util.ParseIntWithError(lastUser.Id)
|
||||
if err != nil {
|
||||
return util.GenerateId(), nil
|
||||
}
|
||||
}
|
||||
|
||||
res := strconv.Itoa(lastUserId + 1)
|
||||
|
@ -240,11 +240,11 @@ func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, str
|
||||
if buffer != nil {
|
||||
faviconUrl, err = GetFaviconUrl(buffer.String())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(faviconUrl, "http") {
|
||||
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
|
||||
fmt.Printf("getFaviconFileBuffer() error, faviconUrl is empty, error = %s\n", err.Error())
|
||||
} else {
|
||||
if !strings.HasPrefix(faviconUrl, "http") {
|
||||
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,10 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
|
||||
}
|
||||
|
||||
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||
content := fmt.Sprintf(provider.Content, code)
|
||||
content := strings.Replace(provider.Content, "%s", code, 1)
|
||||
if user != nil {
|
||||
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
|
||||
}
|
||||
|
||||
if err := IsAllowSend(user, remoteAddr, provider.Category); err != nil {
|
||||
return err
|
||||
|
@ -132,6 +132,9 @@ func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyRes
|
||||
}
|
||||
// Once payment is successful, the Checkout Session will contain a reference to the successful `PaymentIntent`
|
||||
sIntent, err := stripeIntent.Get(sCheckout.PaymentIntent.ID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
productName string
|
||||
productDisplayName string
|
||||
|
@ -17,15 +17,16 @@ package radius
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"layeh.com/radius"
|
||||
"layeh.com/radius/rfc2865"
|
||||
"layeh.com/radius/rfc2866"
|
||||
)
|
||||
|
||||
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a#tab_3
|
||||
func StartRadiusServer() {
|
||||
secret := conf.GetConfigString("radiusSecret")
|
||||
server := radius.PacketServer{
|
||||
@ -74,6 +75,11 @@ func handleAccountingRequest(w radius.ResponseWriter, r *radius.Request) {
|
||||
statusType := rfc2866.AcctStatusType_Get(r.Packet)
|
||||
username := rfc2865.UserName_GetString(r.Packet)
|
||||
organization := rfc2865.Class_GetString(r.Packet)
|
||||
|
||||
if strings.Contains(username, "/") {
|
||||
organization, username = util.GetOwnerAndNameFromId(username)
|
||||
}
|
||||
|
||||
log.Printf("handleAccountingRequest() username=%v, org=%v, statusType=%v", username, organization, statusType)
|
||||
w.Write(r.Response(radius.CodeAccountingResponse))
|
||||
var err error
|
||||
|
@ -73,6 +73,12 @@ func initAPI() {
|
||||
beego.Router("/api/get-default-application", &controllers.ApiController{}, "GET:GetDefaultApplication")
|
||||
beego.Router("/api/get-organization-names", &controllers.ApiController{}, "GET:GetOrganizationNames")
|
||||
|
||||
beego.Router("/api/get-groups", &controllers.ApiController{}, "GET:GetGroups")
|
||||
beego.Router("/api/get-group", &controllers.ApiController{}, "GET:GetGroup")
|
||||
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
|
||||
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
|
||||
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
|
||||
|
||||
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
|
||||
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
|
||||
beego.Router("/api/get-sorted-users", &controllers.ApiController{}, "GET:GetSortedUsers")
|
||||
@ -85,11 +91,34 @@ func initAPI() {
|
||||
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
|
||||
beego.Router("/api/remove-user-from-group", &controllers.ApiController{}, "POST:RemoveUserFromGroup")
|
||||
|
||||
beego.Router("/api/get-groups", &controllers.ApiController{}, "GET:GetGroups")
|
||||
beego.Router("/api/get-group", &controllers.ApiController{}, "GET:GetGroup")
|
||||
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
|
||||
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
|
||||
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
|
||||
beego.Router("/api/get-applications", &controllers.ApiController{}, "GET:GetApplications")
|
||||
beego.Router("/api/get-application", &controllers.ApiController{}, "GET:GetApplication")
|
||||
beego.Router("/api/get-user-application", &controllers.ApiController{}, "GET:GetUserApplication")
|
||||
beego.Router("/api/get-organization-applications", &controllers.ApiController{}, "GET:GetOrganizationApplications")
|
||||
beego.Router("/api/update-application", &controllers.ApiController{}, "POST:UpdateApplication")
|
||||
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
|
||||
beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication")
|
||||
|
||||
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
|
||||
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
|
||||
beego.Router("/api/get-global-providers", &controllers.ApiController{}, "GET:GetGlobalProviders")
|
||||
beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider")
|
||||
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider")
|
||||
beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider")
|
||||
|
||||
beego.Router("/api/get-resources", &controllers.ApiController{}, "GET:GetResources")
|
||||
beego.Router("/api/get-resource", &controllers.ApiController{}, "GET:GetResource")
|
||||
beego.Router("/api/update-resource", &controllers.ApiController{}, "POST:UpdateResource")
|
||||
beego.Router("/api/add-resource", &controllers.ApiController{}, "POST:AddResource")
|
||||
beego.Router("/api/delete-resource", &controllers.ApiController{}, "POST:DeleteResource")
|
||||
beego.Router("/api/upload-resource", &controllers.ApiController{}, "POST:UploadResource")
|
||||
|
||||
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
||||
beego.Router("/api/get-global-certs", &controllers.ApiController{}, "GET:GetGlobalCerts")
|
||||
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
||||
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
|
||||
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
||||
|
||||
beego.Router("/api/get-roles", &controllers.ApiController{}, "GET:GetRoles")
|
||||
beego.Router("/api/get-role", &controllers.ApiController{}, "GET:GetRole")
|
||||
@ -107,12 +136,6 @@ func initAPI() {
|
||||
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
|
||||
beego.Router("/api/upload-permissions", &controllers.ApiController{}, "POST:UploadPermissions")
|
||||
|
||||
beego.Router("/api/enforce", &controllers.ApiController{}, "POST:Enforce")
|
||||
beego.Router("/api/batch-enforce", &controllers.ApiController{}, "POST:BatchEnforce")
|
||||
beego.Router("/api/get-all-objects", &controllers.ApiController{}, "GET:GetAllObjects")
|
||||
beego.Router("/api/get-all-actions", &controllers.ApiController{}, "GET:GetAllActions")
|
||||
beego.Router("/api/get-all-roles", &controllers.ApiController{}, "GET:GetAllRoles")
|
||||
|
||||
beego.Router("/api/get-models", &controllers.ApiController{}, "GET:GetModels")
|
||||
beego.Router("/api/get-model", &controllers.ApiController{}, "GET:GetModel")
|
||||
beego.Router("/api/update-model", &controllers.ApiController{}, "POST:UpdateModel")
|
||||
@ -135,53 +158,11 @@ func initAPI() {
|
||||
beego.Router("/api/add-enforcer", &controllers.ApiController{}, "POST:AddEnforcer")
|
||||
beego.Router("/api/delete-enforcer", &controllers.ApiController{}, "POST:DeleteEnforcer")
|
||||
|
||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "GET:GetEmailAndPhone")
|
||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||
beego.Router("/api/verify-code", &controllers.ApiController{}, "POST:VerifyCode")
|
||||
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||
|
||||
beego.Router("/api/get-ldap-users", &controllers.ApiController{}, "GET:GetLdapUsers")
|
||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "GET:GetLdaps")
|
||||
beego.Router("/api/get-ldap", &controllers.ApiController{}, "GET:GetLdap")
|
||||
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
|
||||
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
|
||||
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
|
||||
beego.Router("/api/sync-ldap-users", &controllers.ApiController{}, "POST:SyncLdapUsers")
|
||||
|
||||
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
|
||||
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
|
||||
beego.Router("/api/get-global-providers", &controllers.ApiController{}, "GET:GetGlobalProviders")
|
||||
beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider")
|
||||
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider")
|
||||
beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider")
|
||||
|
||||
beego.Router("/api/get-applications", &controllers.ApiController{}, "GET:GetApplications")
|
||||
beego.Router("/api/get-application", &controllers.ApiController{}, "GET:GetApplication")
|
||||
beego.Router("/api/get-user-application", &controllers.ApiController{}, "GET:GetUserApplication")
|
||||
beego.Router("/api/get-organization-applications", &controllers.ApiController{}, "GET:GetOrganizationApplications")
|
||||
beego.Router("/api/update-application", &controllers.ApiController{}, "POST:UpdateApplication")
|
||||
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
|
||||
beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication")
|
||||
|
||||
beego.Router("/api/get-resources", &controllers.ApiController{}, "GET:GetResources")
|
||||
beego.Router("/api/get-resource", &controllers.ApiController{}, "GET:GetResource")
|
||||
beego.Router("/api/update-resource", &controllers.ApiController{}, "POST:UpdateResource")
|
||||
beego.Router("/api/add-resource", &controllers.ApiController{}, "POST:AddResource")
|
||||
beego.Router("/api/delete-resource", &controllers.ApiController{}, "POST:DeleteResource")
|
||||
beego.Router("/api/upload-resource", &controllers.ApiController{}, "POST:UploadResource")
|
||||
|
||||
beego.Router("/api/get-tokens", &controllers.ApiController{}, "GET:GetTokens")
|
||||
beego.Router("/api/get-token", &controllers.ApiController{}, "GET:GetToken")
|
||||
beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken")
|
||||
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
|
||||
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
||||
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
||||
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
|
||||
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
|
||||
beego.Router("/api/enforce", &controllers.ApiController{}, "POST:Enforce")
|
||||
beego.Router("/api/batch-enforce", &controllers.ApiController{}, "POST:BatchEnforce")
|
||||
beego.Router("/api/get-all-objects", &controllers.ApiController{}, "GET:GetAllObjects")
|
||||
beego.Router("/api/get-all-actions", &controllers.ApiController{}, "GET:GetAllActions")
|
||||
beego.Router("/api/get-all-roles", &controllers.ApiController{}, "GET:GetAllRoles")
|
||||
|
||||
beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions")
|
||||
beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession")
|
||||
@ -190,43 +171,11 @@ func initAPI() {
|
||||
beego.Router("/api/delete-session", &controllers.ApiController{}, "POST:DeleteSession")
|
||||
beego.Router("/api/is-session-duplicated", &controllers.ApiController{}, "GET:IsSessionDuplicated")
|
||||
|
||||
beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
|
||||
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")
|
||||
beego.Router("/api/update-webhook", &controllers.ApiController{}, "POST:UpdateWebhook")
|
||||
beego.Router("/api/add-webhook", &controllers.ApiController{}, "POST:AddWebhook")
|
||||
beego.Router("/api/delete-webhook", &controllers.ApiController{}, "POST:DeleteWebhook")
|
||||
|
||||
beego.Router("/api/get-syncers", &controllers.ApiController{}, "GET:GetSyncers")
|
||||
beego.Router("/api/get-syncer", &controllers.ApiController{}, "GET:GetSyncer")
|
||||
beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer")
|
||||
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
||||
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
||||
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
|
||||
|
||||
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
||||
beego.Router("/api/get-global-certs", &controllers.ApiController{}, "GET:GetGlobalCerts")
|
||||
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
||||
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
|
||||
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
||||
|
||||
beego.Router("/api/get-subscriptions", &controllers.ApiController{}, "GET:GetSubscriptions")
|
||||
beego.Router("/api/get-subscription", &controllers.ApiController{}, "GET:GetSubscription")
|
||||
beego.Router("/api/update-subscription", &controllers.ApiController{}, "POST:UpdateSubscription")
|
||||
beego.Router("/api/add-subscription", &controllers.ApiController{}, "POST:AddSubscription")
|
||||
beego.Router("/api/delete-subscription", &controllers.ApiController{}, "POST:DeleteSubscription")
|
||||
|
||||
beego.Router("/api/get-plans", &controllers.ApiController{}, "GET:GetPlans")
|
||||
beego.Router("/api/get-plan", &controllers.ApiController{}, "GET:GetPlan")
|
||||
beego.Router("/api/update-plan", &controllers.ApiController{}, "POST:UpdatePlan")
|
||||
beego.Router("/api/add-plan", &controllers.ApiController{}, "POST:AddPlan")
|
||||
beego.Router("/api/delete-plan", &controllers.ApiController{}, "POST:DeletePlan")
|
||||
|
||||
beego.Router("/api/get-pricings", &controllers.ApiController{}, "GET:GetPricings")
|
||||
beego.Router("/api/get-pricing", &controllers.ApiController{}, "GET:GetPricing")
|
||||
beego.Router("/api/update-pricing", &controllers.ApiController{}, "POST:UpdatePricing")
|
||||
beego.Router("/api/add-pricing", &controllers.ApiController{}, "POST:AddPricing")
|
||||
beego.Router("/api/delete-pricing", &controllers.ApiController{}, "POST:DeletePricing")
|
||||
beego.Router("/api/get-tokens", &controllers.ApiController{}, "GET:GetTokens")
|
||||
beego.Router("/api/get-token", &controllers.ApiController{}, "GET:GetToken")
|
||||
beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken")
|
||||
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
|
||||
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
||||
|
||||
beego.Router("/api/get-products", &controllers.ApiController{}, "GET:GetProducts")
|
||||
beego.Router("/api/get-product", &controllers.ApiController{}, "GET:GetProduct")
|
||||
@ -244,6 +193,64 @@ func initAPI() {
|
||||
beego.Router("/api/notify-payment/?:owner/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
|
||||
beego.Router("/api/invoice-payment", &controllers.ApiController{}, "POST:InvoicePayment")
|
||||
|
||||
beego.Router("/api/get-plans", &controllers.ApiController{}, "GET:GetPlans")
|
||||
beego.Router("/api/get-plan", &controllers.ApiController{}, "GET:GetPlan")
|
||||
beego.Router("/api/update-plan", &controllers.ApiController{}, "POST:UpdatePlan")
|
||||
beego.Router("/api/add-plan", &controllers.ApiController{}, "POST:AddPlan")
|
||||
beego.Router("/api/delete-plan", &controllers.ApiController{}, "POST:DeletePlan")
|
||||
|
||||
beego.Router("/api/get-pricings", &controllers.ApiController{}, "GET:GetPricings")
|
||||
beego.Router("/api/get-pricing", &controllers.ApiController{}, "GET:GetPricing")
|
||||
beego.Router("/api/update-pricing", &controllers.ApiController{}, "POST:UpdatePricing")
|
||||
beego.Router("/api/add-pricing", &controllers.ApiController{}, "POST:AddPricing")
|
||||
beego.Router("/api/delete-pricing", &controllers.ApiController{}, "POST:DeletePricing")
|
||||
|
||||
beego.Router("/api/get-subscriptions", &controllers.ApiController{}, "GET:GetSubscriptions")
|
||||
beego.Router("/api/get-subscription", &controllers.ApiController{}, "GET:GetSubscription")
|
||||
beego.Router("/api/update-subscription", &controllers.ApiController{}, "POST:UpdateSubscription")
|
||||
beego.Router("/api/add-subscription", &controllers.ApiController{}, "POST:AddSubscription")
|
||||
beego.Router("/api/delete-subscription", &controllers.ApiController{}, "POST:DeleteSubscription")
|
||||
|
||||
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
||||
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
|
||||
beego.Router("/api/health", &controllers.ApiController{}, "GET:Health")
|
||||
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
|
||||
beego.Handler("/api/metrics", promhttp.Handler())
|
||||
|
||||
beego.Router("/api/get-syncers", &controllers.ApiController{}, "GET:GetSyncers")
|
||||
beego.Router("/api/get-syncer", &controllers.ApiController{}, "GET:GetSyncer")
|
||||
beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer")
|
||||
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
||||
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
||||
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
|
||||
|
||||
beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
|
||||
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")
|
||||
beego.Router("/api/update-webhook", &controllers.ApiController{}, "POST:UpdateWebhook")
|
||||
beego.Router("/api/add-webhook", &controllers.ApiController{}, "POST:AddWebhook")
|
||||
beego.Router("/api/delete-webhook", &controllers.ApiController{}, "POST:DeleteWebhook")
|
||||
|
||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "GET:GetEmailAndPhone")
|
||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||
beego.Router("/api/verify-code", &controllers.ApiController{}, "POST:VerifyCode")
|
||||
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||
|
||||
beego.Router("/api/get-ldap-users", &controllers.ApiController{}, "GET:GetLdapUsers")
|
||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "GET:GetLdaps")
|
||||
beego.Router("/api/get-ldap", &controllers.ApiController{}, "GET:GetLdap")
|
||||
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
|
||||
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
|
||||
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
|
||||
beego.Router("/api/sync-ldap-users", &controllers.ApiController{}, "POST:SyncLdapUsers")
|
||||
|
||||
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
||||
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
|
||||
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
|
||||
|
||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||
beego.Router("/api/send-notification", &controllers.ApiController{}, "POST:SendNotification")
|
||||
@ -259,13 +266,6 @@ func initAPI() {
|
||||
beego.Router("/api/delete-mfa", &controllers.ApiController{}, "POST:DeleteMfa")
|
||||
beego.Router("/api/set-preferred-mfa", &controllers.ApiController{}, "POST:SetPreferredMfa")
|
||||
|
||||
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
||||
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
|
||||
beego.Router("/api/health", &controllers.ApiController{}, "GET:Health")
|
||||
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
|
||||
|
||||
beego.Handler("/api/metrics", promhttp.Handler())
|
||||
|
||||
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
||||
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
||||
|
||||
|
@ -129,7 +129,7 @@ func StaticFilter(ctx *context.Context) {
|
||||
path += urlPath
|
||||
}
|
||||
|
||||
if !util.FileExist(path) {
|
||||
if strings.Contains(path, "/../") || !util.FileExist(path) {
|
||||
path = webBuildFolder + "/index.html"
|
||||
}
|
||||
if !util.FileExist(path) {
|
||||
|
@ -22,12 +22,13 @@ import (
|
||||
|
||||
func NewAwsS3StorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
|
||||
sp := s3.New(&s3.Config{
|
||||
AccessID: clientId,
|
||||
AccessKey: clientSecret,
|
||||
Region: region,
|
||||
Bucket: bucket,
|
||||
Endpoint: endpoint,
|
||||
ACL: awss3.BucketCannedACLPublicRead,
|
||||
AccessID: clientId,
|
||||
AccessKey: clientSecret,
|
||||
Region: region,
|
||||
Bucket: bucket,
|
||||
Endpoint: endpoint,
|
||||
S3Endpoint: endpoint,
|
||||
ACL: awss3.BucketCannedACLPublicRead,
|
||||
})
|
||||
|
||||
return sp
|
||||
|
@ -19,13 +19,15 @@ import (
|
||||
"github.com/casdoor/oss/googlecloud"
|
||||
)
|
||||
|
||||
func NewGoogleCloudStorageProvider(clientId string, clientSecret string, bucket string, endpoint string) oss.StorageInterface {
|
||||
sp, _ := googlecloud.New(&googlecloud.Config{
|
||||
AccessID: clientId,
|
||||
AccessKey: clientSecret,
|
||||
Bucket: bucket,
|
||||
Endpoint: endpoint,
|
||||
func NewGoogleCloudStorageProvider(clientSecret string, bucket string, endpoint string) oss.StorageInterface {
|
||||
sp, err := googlecloud.New(&googlecloud.Config{
|
||||
ServiceAccountJson: clientSecret,
|
||||
Bucket: bucket,
|
||||
Endpoint: endpoint,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sp
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
||||
case "Qiniu Cloud Kodo":
|
||||
return NewQiniuCloudKodoStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
case "Google Cloud Storage":
|
||||
return NewGoogleCloudStorageProvider(clientId, clientSecret, bucket, endpoint)
|
||||
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
10
util/path.go
10
util/path.go
@ -72,7 +72,15 @@ func GetUrlPath(urlString string) string {
|
||||
}
|
||||
|
||||
func GetUrlHost(urlString string) string {
|
||||
u, _ := url.Parse(urlString)
|
||||
if urlString == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
u, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,19 @@ func ParseInt(s string) int {
|
||||
return i
|
||||
}
|
||||
|
||||
func ParseIntWithError(s string) (int, error) {
|
||||
if s == "" {
|
||||
return 0, fmt.Errorf("ParseIntWithError() error, empty string")
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func ParseFloat(s string) float64 {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
@ -309,3 +322,23 @@ func GetUsernameFromEmail(email string) string {
|
||||
return tokens[0]
|
||||
}
|
||||
}
|
||||
|
||||
func StringToInterfaceArray(array []string) []interface{} {
|
||||
var interfaceArray []interface{}
|
||||
for _, v := range array {
|
||||
interfaceArray = append(interfaceArray, v)
|
||||
}
|
||||
return interfaceArray
|
||||
}
|
||||
|
||||
func StringToInterfaceArray2d(arrays [][]string) [][]interface{} {
|
||||
var interfaceArrays [][]interface{}
|
||||
for _, req := range arrays {
|
||||
var interfaceArray []interface{}
|
||||
for _, r := range req {
|
||||
interfaceArray = append(interfaceArray, r)
|
||||
}
|
||||
interfaceArrays = append(interfaceArrays, interfaceArray)
|
||||
}
|
||||
return interfaceArrays
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"i18n-iso-countries": "^7.0.0",
|
||||
"i18next": "^19.8.9",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"libphonenumber-js": "^1.10.19",
|
||||
"moment": "^2.29.1",
|
||||
"qrcode.react": "^3.1.0",
|
||||
|
111
web/src/App.js
111
web/src/App.js
@ -15,62 +15,62 @@
|
||||
import React, {Component} from "react";
|
||||
import "./App.less";
|
||||
import {Helmet} from "react-helmet";
|
||||
import Dashboard from "./basic/Dashboard";
|
||||
import ShortcutsPage from "./basic/ShortcutsPage";
|
||||
import * as Setting from "./Setting";
|
||||
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||
import {AppstoreTwoTone, BarsOutlined, DeploymentUnitOutlined, DollarTwoTone, DownOutlined, GithubOutlined, HomeTwoTone, InfoCircleFilled, LockTwoTone, LogoutOutlined, SafetyCertificateTwoTone, SettingOutlined, SettingTwoTone, ShareAltOutlined, WalletTwoTone} from "@ant-design/icons";
|
||||
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result, Tooltip} from "antd";
|
||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import Dashboard from "./basic/Dashboard";
|
||||
import ShortcutsPage from "./basic/ShortcutsPage";
|
||||
import AppListPage from "./basic/AppListPage";
|
||||
import OrganizationListPage from "./OrganizationListPage";
|
||||
import OrganizationEditPage from "./OrganizationEditPage";
|
||||
import GroupEditPage from "./GroupEdit";
|
||||
import GroupListPage from "./GroupList";
|
||||
import GroupTreePage from "./GroupTreePage";
|
||||
import UserListPage from "./UserListPage";
|
||||
import UserEditPage from "./UserEditPage";
|
||||
import ApplicationListPage from "./ApplicationListPage";
|
||||
import ApplicationEditPage from "./ApplicationEditPage";
|
||||
import ProviderListPage from "./ProviderListPage";
|
||||
import ProviderEditPage from "./ProviderEditPage";
|
||||
import ResourceListPage from "./ResourceListPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import RoleListPage from "./RoleListPage";
|
||||
import RoleEditPage from "./RoleEditPage";
|
||||
import PermissionListPage from "./PermissionListPage";
|
||||
import PermissionEditPage from "./PermissionEditPage";
|
||||
import ModelListPage from "./ModelListPage";
|
||||
import ModelEditPage from "./ModelEditPage";
|
||||
import AdapterListPage from "./AdapterListPage";
|
||||
import AdapterEditPage from "./AdapterEditPage";
|
||||
import EnforcerEditPage from "./EnforcerEditPage";
|
||||
import EnforcerListPage from "./EnforcerListPage";
|
||||
import GroupTreePage from "./GroupTreePage";
|
||||
import GroupEditPage from "./GroupEdit";
|
||||
import GroupListPage from "./GroupList";
|
||||
import ProviderListPage from "./ProviderListPage";
|
||||
import ProviderEditPage from "./ProviderEditPage";
|
||||
import ApplicationListPage from "./ApplicationListPage";
|
||||
import ApplicationEditPage from "./ApplicationEditPage";
|
||||
import ResourceListPage from "./ResourceListPage";
|
||||
import LdapEditPage from "./LdapEditPage";
|
||||
import LdapSyncPage from "./LdapSyncPage";
|
||||
import SessionListPage from "./SessionListPage";
|
||||
import TokenListPage from "./TokenListPage";
|
||||
import TokenEditPage from "./TokenEditPage";
|
||||
import WebhookListPage from "./WebhookListPage";
|
||||
import WebhookEditPage from "./WebhookEditPage";
|
||||
import SyncerListPage from "./SyncerListPage";
|
||||
import SyncerEditPage from "./SyncerEditPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import SubscriptionListPage from "./SubscriptionListPage";
|
||||
import SubscriptionEditPage from "./SubscriptionEditPage";
|
||||
import PricingListPage from "./PricingListPage";
|
||||
import PricingEditPage from "./PricingEditPage";
|
||||
import PlanListPage from "./PlanListPage";
|
||||
import PlanEditPage from "./PlanEditPage";
|
||||
import ProductListPage from "./ProductListPage";
|
||||
import ProductEditPage from "./ProductEditPage";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
import PaymentListPage from "./PaymentListPage";
|
||||
import PaymentEditPage from "./PaymentEditPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import ModelListPage from "./ModelListPage";
|
||||
import ModelEditPage from "./ModelEditPage";
|
||||
import AdapterListPage from "./AdapterListPage";
|
||||
import AdapterEditPage from "./AdapterEditPage";
|
||||
import SessionListPage from "./SessionListPage";
|
||||
import MfaSetupPage from "./auth/MfaSetupPage";
|
||||
import PricingListPage from "./PricingListPage";
|
||||
import PricingEditPage from "./PricingEditPage";
|
||||
import PlanListPage from "./PlanListPage";
|
||||
import PlanEditPage from "./PlanEditPage";
|
||||
import SubscriptionListPage from "./SubscriptionListPage";
|
||||
import SubscriptionEditPage from "./SubscriptionEditPage";
|
||||
import SystemInfo from "./SystemInfo";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import AppListPage from "./basic/AppListPage";
|
||||
import SyncerListPage from "./SyncerListPage";
|
||||
import SyncerEditPage from "./SyncerEditPage";
|
||||
import WebhookListPage from "./WebhookListPage";
|
||||
import WebhookEditPage from "./WebhookEditPage";
|
||||
import LdapEditPage from "./LdapEditPage";
|
||||
import LdapSyncPage from "./LdapSyncPage";
|
||||
import MfaSetupPage from "./auth/MfaSetupPage";
|
||||
import CustomGithubCorner from "./common/CustomGithubCorner";
|
||||
import * as Conf from "./Conf";
|
||||
|
||||
@ -514,48 +514,47 @@ class App extends Component {
|
||||
<Route exact path="/groups/:organizationName/:groupName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs/:organizationName/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/enforcers" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/enforcers/:organizationName/:enforcerName" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
||||
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
|
||||
<Route exact path="/ldap/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/ldap/sync/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/enforcers" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/enforcers/:organizationName/:enforcerName" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs/:organizationName/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/plans" render={(props) => this.renderLoginIfNotLoggedIn(<PlanListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/plans/:organizationName/:planName" render={(props) => this.renderLoginIfNotLoggedIn(<PlanEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/pricings" render={(props) => this.renderLoginIfNotLoggedIn(<PricingListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/pricings/:organizationName/:pricingName" render={(props) => this.renderLoginIfNotLoggedIn(<PricingEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/subscriptions" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/subscriptions/:organizationName/:subscriptionName" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:organizationName/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:organizationName/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:organizationName/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:organizationName/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/plans" render={(props) => this.renderLoginIfNotLoggedIn(<PlanListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/plans/:organizationName/:planName" render={(props) => this.renderLoginIfNotLoggedIn(<PlanEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/pricings" render={(props) => this.renderLoginIfNotLoggedIn(<PricingListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/pricings/:organizationName/:pricingName" render={(props) => this.renderLoginIfNotLoggedIn(<PricingEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/subscriptions" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/subscriptions/:organizationName/:subscriptionName" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/ldap/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/ldap/sync/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/mfa/setup" render={(props) => this.renderLoginIfNotLoggedIn(<MfaSetupPage account={this.state.account} onfinish={() => this.setState({requiredEnableMfa: false})} {...props} />)} />
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
||||
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, ConfigProvider, Input, List, Popover, Radio, Result, Row, Select, Space, Switch, Upload} from "antd";
|
||||
import {Button, Card, Col, ConfigProvider, Input, InputNumber, List, Popover, Radio, Result, Row, Select, Space, Switch, Upload} from "antd";
|
||||
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
@ -199,7 +199,7 @@ class ApplicationEditPage extends React.Component {
|
||||
}
|
||||
|
||||
parseApplicationField(key, value) {
|
||||
if (["expireInHours", "refreshExpireInHours", "offset"].includes(key)) {
|
||||
if (["offset"].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
}
|
||||
return value;
|
||||
@ -394,8 +394,8 @@ class ApplicationEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("application:Token expire"), i18next.t("application:Token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input style={{width: "150px"}} value={this.state.application.expireInHours} suffix="Hours" onChange={e => {
|
||||
this.updateApplicationField("expireInHours", e.target.value);
|
||||
<InputNumber style={{width: "150px"}} value={this.state.application.expireInHours} min={1} step={1} precision={0} addonAfter="Hours" onChange={value => {
|
||||
this.updateApplicationField("expireInHours", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
@ -404,8 +404,28 @@ class ApplicationEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("application:Refresh token expire"), i18next.t("application:Refresh token expire - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input style={{width: "150px"}} value={this.state.application.refreshExpireInHours} suffix="Hours" onChange={e => {
|
||||
this.updateApplicationField("refreshExpireInHours", e.target.value);
|
||||
<InputNumber style={{width: "150px"}} value={this.state.application.refreshExpireInHours} min={1} step={1} precision={0} addonAfter="Hours" onChange={value => {
|
||||
this.updateApplicationField("refreshExpireInHours", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Failed signin limit"), i18next.t("application:Failed signin limit - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber style={{width: "150px"}} value={this.state.application.failedSigninLimit} min={1} step={1} precision={0} addonAfter="Times" onChange={value => {
|
||||
this.updateApplicationField("failedSigninLimit", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Failed signin frozen time"), i18next.t("application:Failed signin frozen time - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber style={{width: "150px"}} value={this.state.application.failedSigninfrozenTime} min={1} step={1} precision={0} addonAfter="Minutes" onChange={value => {
|
||||
this.updateApplicationField("failedSigninfrozenTime", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
@ -676,7 +696,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<br />
|
||||
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}`);
|
||||
Setting.showMessage("success", i18next.t("application:SAML metadata URL copied to clipboard successfully"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("application:Copy SAML metadata URL")}
|
||||
@ -885,7 +905,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<Space>
|
||||
<Button icon={<CopyOutlined />} onClick={() => {
|
||||
copy(item.code);
|
||||
Setting.showMessage("success", i18next.t("application:Invitation code copied to clipboard successfully"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}
|
||||
}>
|
||||
{i18next.t("general:Copy")}
|
||||
@ -939,7 +959,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<Col span={previewGrid}>
|
||||
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||
copy(`${window.location.origin}${signUpUrl}`);
|
||||
Setting.showMessage("success", i18next.t("application:Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("application:Copy signup page URL")}
|
||||
@ -971,7 +991,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<Col span={previewGrid}>
|
||||
<Button style={{marginBottom: "10px", marginTop: Setting.isMobile() ? "15px" : "0"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||
copy(`${window.location.origin}${signInUrl}`);
|
||||
Setting.showMessage("success", i18next.t("application:Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("application:Copy signin page URL")}
|
||||
@ -1004,7 +1024,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<Col span={previewGrid}>
|
||||
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||
copy(`${window.location.origin}${promptUrl}`);
|
||||
Setting.showMessage("success", i18next.t("application:Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("application:Copy prompt page URL")}
|
||||
|
@ -230,7 +230,7 @@ class CertEditPage extends React.Component {
|
||||
<Col span={editorWidth} >
|
||||
<Button style={{marginRight: "10px", marginBottom: "10px"}} disabled={this.state.cert.certificate === ""} onClick={() => {
|
||||
copy(this.state.cert.certificate);
|
||||
Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("cert:Copy certificate")}
|
||||
@ -253,7 +253,7 @@ class CertEditPage extends React.Component {
|
||||
<Col span={editorWidth} >
|
||||
<Button style={{marginRight: "10px", marginBottom: "10px"}} disabled={this.state.cert.privateKey === ""} onClick={() => {
|
||||
copy(this.state.cert.privateKey);
|
||||
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("cert:Copy private key")}
|
||||
|
@ -303,7 +303,7 @@ class PermissionEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select disabled={!this.hasRoleDefinition(this.state.model)} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
|
||||
<Select disabled={!this.hasRoleDefinition(this.state.model)} placeholder={this.hasRoleDefinition(this.state.model) ? "" : "This field is disabled because the model is empty or it doesn't support RBAC (in another word, doesn't contain [role_definition])"} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
|
||||
onChange={(value => {this.updatePermissionField("roles", value);})}
|
||||
options={[
|
||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||
@ -323,7 +323,7 @@ class PermissionEditPage extends React.Component {
|
||||
})}
|
||||
options={[
|
||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||
...this.state.permission.domains.map((domain) => Setting.getOption(domain, domain)),
|
||||
...this.state.permission.domains.filter(domain => domain !== "*").map((domain) => Setting.getOption(domain, domain)),
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
|
@ -44,7 +44,7 @@ class PermissionListPage extends BaseListPage {
|
||||
submitter: this.props.account.name,
|
||||
approver: "",
|
||||
approveTime: "",
|
||||
state: "Pending",
|
||||
state: Setting.isLocalAdminUser(this.props.account) ? "Approved" : "Pending",
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -287,7 +287,7 @@ class PricingEditPage extends React.Component {
|
||||
<Col>
|
||||
<Button style={{marginBottom: "10px", marginTop: Setting.isMobile() ? "15px" : "0"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||
copy(`${window.location.origin}${pricingUrl}`);
|
||||
Setting.showMessage("success", i18next.t("pricing:pricing page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("pricing:Copy pricing page URL")}
|
||||
|
@ -29,6 +29,14 @@ import {CaptchaPreview} from "./common/CaptchaPreview";
|
||||
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
||||
import * as Web3Auth from "./auth/Web3Auth";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
|
||||
require("codemirror/theme/material-darker.css");
|
||||
require("codemirror/mode/htmlmixed/htmlmixed");
|
||||
require("codemirror/mode/xml/xml");
|
||||
require("codemirror/mode/css/css");
|
||||
|
||||
const {Option} = Select;
|
||||
const {TextArea} = Input;
|
||||
|
||||
@ -197,6 +205,12 @@ class ProviderEditPage extends React.Component {
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
case "Storage":
|
||||
if (provider.type === "Google Cloud Storage") {
|
||||
return Setting.getLabel(i18next.t("provider:Service account JSON"), i18next.t("provider:Service account JSON - Tooltip"));
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
case "Email":
|
||||
if (provider.type === "Azure ACS") {
|
||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||
@ -305,6 +319,9 @@ class ProviderEditPage extends React.Component {
|
||||
} else if (provider.type === "Infoflow") {
|
||||
text = i18next.t("provider:Agent ID");
|
||||
tooltip = i18next.t("provider:Agent ID - Tooltip");
|
||||
} else if (provider.type === "AzureADB2C") {
|
||||
text = i18next.t("provider:User flow");
|
||||
tooltip = i18next.t("provider:User flow - Tooltip");
|
||||
}
|
||||
} else if (provider.category === "SMS") {
|
||||
if (provider.type === "Twilio SMS" || provider.type === "Azure ACS") {
|
||||
@ -471,7 +488,7 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField("port", 465);
|
||||
this.updateProviderField("disableSsl", false);
|
||||
this.updateProviderField("title", "Casdoor Verification Code");
|
||||
this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.");
|
||||
this.updateProviderField("content", Setting.getDefaultHtmlEmailContent());
|
||||
this.updateProviderField("receiver", this.props.account.email);
|
||||
} else if (value === "SMS") {
|
||||
this.updateProviderField("type", "Twilio SMS");
|
||||
@ -521,6 +538,13 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField("scopes", "openid profile email");
|
||||
this.updateProviderField("customTokenUrl", "https://door.casdoor.com/api/login/oauth/access_token");
|
||||
this.updateProviderField("customUserInfoUrl", "https://door.casdoor.com/api/userinfo");
|
||||
} else if (value === "Custom HTTP SMS") {
|
||||
this.updateProviderField("endpoint", "https://example.com/send-custom-http-sms");
|
||||
this.updateProviderField("method", "GET");
|
||||
this.updateProviderField("title", "code");
|
||||
} else if (value === "Custom HTTP Email") {
|
||||
this.updateProviderField("endpoint", "https://example.com/send-custom-http-email");
|
||||
this.updateProviderField("method", "POST");
|
||||
} else if (value === "Custom HTTP") {
|
||||
this.updateProviderField("method", "GET");
|
||||
this.updateProviderField("title", "");
|
||||
@ -668,9 +692,11 @@ class ProviderEditPage extends React.Component {
|
||||
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||
|
||||
(this.state.provider.category === "Web3") ||
|
||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ||
|
||||
(this.state.provider.category === "SMS" && this.state.provider.type === "Custom HTTP SMS") ||
|
||||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP")) ? null : (
|
||||
<React.Fragment>
|
||||
{
|
||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||
|
||||
(this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ||
|
||||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Line" || this.state.provider.type === "Telegram" || this.state.provider.type === "Bark" || this.state.provider.type === "Discord" || this.state.provider.type === "Slack" || this.state.provider.type === "Pushbullet" || this.state.provider.type === "Pushover" || this.state.provider.type === "Lark" || this.state.provider.type === "Microsoft Teams")) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -743,7 +769,7 @@ class ProviderEditPage extends React.Component {
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
|
||||
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||
@ -756,7 +782,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{this.state.provider.category === "Storage" ? (
|
||||
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email"].includes(this.state.provider.type) ? (
|
||||
<div>
|
||||
{["Local File System"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -770,7 +796,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
||||
@ -782,7 +808,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Local File System"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
||||
@ -794,17 +820,19 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.pathPrefix} onChange={e => {
|
||||
this.updateProviderField("pathPrefix", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{["MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.pathPrefix} onChange={e => {
|
||||
this.updateProviderField("pathPrefix", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Custom HTTP SMS", "MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||
@ -946,22 +974,47 @@ class ProviderEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("provider:Email content"), i18next.t("provider:Email content - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<TextArea autoSize={{minRows: 3, maxRows: 100}} value={this.state.provider.content} onChange={e => {
|
||||
this.updateProviderField("content", e.target.value);
|
||||
}} />
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.")} >
|
||||
{i18next.t("provider:Reset to Default Text")}
|
||||
</Button>
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => this.updateProviderField("content", Setting.getDefaultHtmlEmailContent())} >
|
||||
{i18next.t("provider:Reset to Default HTML")}
|
||||
</Button>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col span={Setting.isMobile() ? 22 : 11}>
|
||||
<div style={{height: "300px", margin: "10px"}}>
|
||||
<CodeMirror
|
||||
value={this.state.provider.content}
|
||||
options={{mode: "htmlmixed", theme: "material-darker"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
this.updateProviderField("content", value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={1} />
|
||||
<Col span={Setting.isMobile() ? 22 : 11}>
|
||||
<div style={{margin: "10px"}}>
|
||||
<div dangerouslySetInnerHTML={{__html: this.state.provider.content.replace("%s", "123456").replace("%{user.friendlyName}", Setting.getFriendlyUserName(this.props.account))}} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={4} >
|
||||
<Input value={this.state.provider.receiver} placeholder = {i18next.t("user:Input your email")} onChange={e => {
|
||||
this.updateProviderField("receiver", e.target.value);
|
||||
}} />
|
||||
<Col span={4}>
|
||||
<Input value={this.state.provider.receiver} placeholder={i18next.t("user:Input your email")}
|
||||
onChange={e => {
|
||||
this.updateProviderField("receiver", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
{["Azure ACS"].includes(this.state.provider.type) ? null : (
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||
{i18next.t("provider:Test SMTP Connection")}
|
||||
</Button>
|
||||
)}
|
||||
@ -974,7 +1027,7 @@ class ProviderEditPage extends React.Component {
|
||||
</React.Fragment>
|
||||
) : this.state.provider.category === "SMS" ? (
|
||||
<React.Fragment>
|
||||
{["Twilio SMS", "Amazon SNS", "Azure ACS", "Msg91 SMS", "Infobip SMS"].includes(this.state.provider.type) ?
|
||||
{["Custom HTTP SMS", "Twilio SMS", "Amazon SNS", "Azure ACS", "Msg91 SMS", "Infobip SMS"].includes(this.state.provider.type) ?
|
||||
null :
|
||||
(<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
@ -988,7 +1041,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{["Infobip SMS"].includes(this.state.provider.type) ?
|
||||
{["Custom HTTP SMS", "Infobip SMS"].includes(this.state.provider.type) ?
|
||||
null :
|
||||
(<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
@ -1002,6 +1055,39 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{
|
||||
!["Custom HTTP SMS", "Custom HTTP Email"].includes(this.state.provider.type) ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.method} onChange={value => {
|
||||
this.updateProviderField("method", value);
|
||||
}}>
|
||||
{
|
||||
[
|
||||
{id: "GET", name: "GET"},
|
||||
{id: "POST", name: "POST"},
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.title} onChange={e => {
|
||||
this.updateProviderField("title", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:SMS Test"), i18next.t("provider:SMS Test - Tooltip"))} :
|
||||
@ -1026,7 +1112,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
<Col span={2} >
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||
disabled={!Setting.isValidPhone(this.state.provider.receiver)}
|
||||
disabled={!Setting.isValidPhone(this.state.provider.receiver) && (this.state.provider.type !== "Custom HTTP SMS" || this.state.provider.endpoint === "")}
|
||||
onClick={() => ProviderEditTestSms.sendTestSms(this.state.provider, "+" + Setting.getCountryCode(this.state.provider.content) + this.state.provider.receiver)} >
|
||||
{i18next.t("provider:Send Testing SMS")}
|
||||
</Button>
|
||||
@ -1110,7 +1196,7 @@ class ProviderEditPage extends React.Component {
|
||||
<Col span={1}>
|
||||
<Button type="primary" onClick={() => {
|
||||
copy(`${authConfig.serverUrl}/api/acs`);
|
||||
Setting.showMessage("success", i18next.t("provider:Link copied to clipboard successfully"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}>
|
||||
{i18next.t("provider:Copy")}
|
||||
</Button>
|
||||
@ -1126,7 +1212,7 @@ class ProviderEditPage extends React.Component {
|
||||
<Col span={1}>
|
||||
<Button type="primary" onClick={() => {
|
||||
copy(`${authConfig.serverUrl}/api/acs`);
|
||||
Setting.showMessage("success", i18next.t("provider:Link copied to clipboard successfully"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}>
|
||||
{i18next.t("provider:Copy")}
|
||||
</Button>
|
||||
@ -1155,7 +1241,7 @@ class ProviderEditPage extends React.Component {
|
||||
(this.state.provider.type === "Alipay") ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Root Cert"), i18next.t("general:Root Cert - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("general:Root cert"), i18next.t("general:Root cert - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.metadata} onChange={(value => {this.updateProviderField("metadata", value);})}>
|
||||
|
@ -248,7 +248,7 @@ class ResourceListPage extends BaseListPage {
|
||||
<div>
|
||||
<Button onClick={() => {
|
||||
copy(record.url);
|
||||
Setting.showMessage("success", i18next.t("provider:Link copied to clipboard successfully"));
|
||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("resource:Copy Link")}
|
||||
|
@ -143,6 +143,10 @@ export const OtherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/social_msg91.ico`,
|
||||
url: "https://control.msg91.com/app/",
|
||||
},
|
||||
"Custom HTTP SMS": {
|
||||
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||
url: "https://casdoor.org/docs/provider/sms/overview",
|
||||
},
|
||||
"Mock SMS": {
|
||||
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||
url: "",
|
||||
@ -165,6 +169,10 @@ export const OtherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/social_azure.png`,
|
||||
url: "https://learn.microsoft.com/zh-cn/azure/communication-services",
|
||||
},
|
||||
"Custom HTTP Email": {
|
||||
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||
url: "https://casdoor.org/docs/provider/email/overview",
|
||||
},
|
||||
},
|
||||
Storage: {
|
||||
"Local File System": {
|
||||
@ -916,7 +924,8 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "Casdoor", name: "Casdoor"},
|
||||
{id: "Infoflow", name: "Infoflow"},
|
||||
{id: "Apple", name: "Apple"},
|
||||
{id: "AzureAD", name: "AzureAD"},
|
||||
{id: "AzureAD", name: "Azure AD"},
|
||||
{id: "AzureADB2C", name: "Azure AD B2C"},
|
||||
{id: "Slack", name: "Slack"},
|
||||
{id: "Steam", name: "Steam"},
|
||||
{id: "Bilibili", name: "Bilibili"},
|
||||
@ -981,6 +990,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "SUBMAIL", name: "SUBMAIL"},
|
||||
{id: "Mailtrap", name: "Mailtrap"},
|
||||
{id: "Azure ACS", name: "Azure ACS"},
|
||||
{id: "Custom HTTP Email", name: "Custom HTTP Email"},
|
||||
]
|
||||
);
|
||||
} else if (category === "SMS") {
|
||||
@ -989,6 +999,8 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "Aliyun SMS", name: "Alibaba Cloud SMS"},
|
||||
{id: "Amazon SNS", name: "Amazon SNS"},
|
||||
{id: "Azure ACS", name: "Azure ACS"},
|
||||
{id: "Custom HTTP SMS", name: "Custom HTTP SMS"},
|
||||
{id: "Mock SMS", name: "Mock SMS"},
|
||||
{id: "Infobip SMS", name: "Infobip SMS"},
|
||||
{id: "Tencent Cloud SMS", name: "Tencent Cloud SMS"},
|
||||
{id: "Baidu Cloud SMS", name: "Baidu Cloud SMS"},
|
||||
@ -1393,3 +1405,53 @@ export function getCurrencySymbol(currency) {
|
||||
return currency;
|
||||
}
|
||||
}
|
||||
|
||||
export function getFriendlyUserName(account) {
|
||||
if (account.firstName !== "" && account.lastName !== "") {
|
||||
return `${account.firstName}, ${account.lastName}`;
|
||||
} else if (account.displayName !== "") {
|
||||
return account.displayName;
|
||||
} else if (account.name !== "") {
|
||||
return account.name;
|
||||
} else {
|
||||
return account.id;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultHtmlEmailContent() {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Verification Code Email</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; }
|
||||
.email-container { width: 600px; margin: 0 auto; }
|
||||
.header { text-align: center; }
|
||||
.code { font-size: 24px; margin: 20px 0; text-align: center; }
|
||||
.footer { font-size: 12px; text-align: center; margin-top: 50px; }
|
||||
.footer a { color: #000; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<h3>Casbin Organization</h3>
|
||||
<img src="https://cdn.casbin.org/img/casdoor-logo_1185x256.png" alt="Casdoor Logo" width="300">
|
||||
</div>
|
||||
<p><strong>%{user.friendlyName}</strong>, here is your verification code</p>
|
||||
<p>Use this code for your transaction. It's valid for 5 minutes</p>
|
||||
<div class="code">
|
||||
%s
|
||||
</div>
|
||||
<p>Thanks</p>
|
||||
<p>Casbin Team</p>
|
||||
<hr>
|
||||
<div class="footer">
|
||||
<p>Casdoor is a brand operated by Casbin organization. For more info please refer to <a href="https://casdoor.org">https://casdoor.org</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user