mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-15 04:13:49 +08:00
Compare commits
143 Commits
Author | SHA1 | Date | |
---|---|---|---|
319031da28 | |||
d20f3eb039 | |||
3e13e61d8f | |||
1260354b36 | |||
af79fdedf2 | |||
02333f2f0c | |||
79bd58e0e6 | |||
de73ff0e60 | |||
a9d662f1bd | |||
65dcbd2236 | |||
6455734807 | |||
2eefeaffa7 | |||
04eaad1c80 | |||
9f084a0799 | |||
293b9f1036 | |||
437376c472 | |||
cc528c5d8c | |||
54e2055ffb | |||
983a30a2e0 | |||
37d0157d41 | |||
d4dc236770 | |||
596742d782 | |||
ce921c00cd | |||
3830e443b0 | |||
9092cad631 | |||
0b5ecca5c8 | |||
3d9b305bbb | |||
0217e359e7 | |||
695a612e77 | |||
645d53e2c6 | |||
73b9d73f64 | |||
c6675ee4e6 | |||
6f0b7f3f24 | |||
776a682fae | |||
96a3db21a1 | |||
c33d537ac1 | |||
5214d48486 | |||
e360b06d12 | |||
3c871c38df | |||
7df043fb15 | |||
cb542ae46a | |||
3699177837 | |||
3a6846b32c | |||
50586a9716 | |||
9201992140 | |||
eb39e9e044 | |||
5b27f939b8 | |||
69ee6a6f7e | |||
bf6d5e529b | |||
55fd31f575 | |||
05c063ac24 | |||
38da63e73c | |||
cb13d693e6 | |||
d699774179 | |||
84a7fdcd07 | |||
2cd6f9df8e | |||
eea2e1d271 | |||
48c5bd942c | |||
d01d63d82a | |||
e4fd9cca92 | |||
8d531b8880 | |||
b1589e11eb | |||
b32a772a77 | |||
7e4562efe1 | |||
3a6ab4cfc6 | |||
fba4801a41 | |||
da21c92815 | |||
66c15578b1 | |||
f272be67ab | |||
e4c36d407f | |||
4c1915b014 | |||
6c2b172aae | |||
95f4f4cb6d | |||
511aefb706 | |||
1003639e5b | |||
fe53e90d37 | |||
8c73cb5395 | |||
06ebc04032 | |||
0ee98e2582 | |||
d25508fa56 | |||
916a55b633 | |||
a6c7b95f97 | |||
4f8dd771bc | |||
e0028f5eed | |||
6d6cbc7e6f | |||
ee8c2650c3 | |||
f3ea39d20c | |||
e78d9e5d2b | |||
19209718ea | |||
e75d26260a | |||
6572ab69ce | |||
8db87a7559 | |||
0dcccfc19c | |||
96219442f5 | |||
903745c540 | |||
df741805cd | |||
ee5c3f3f39 | |||
714f69be7b | |||
0d12972e92 | |||
78b62c28ab | |||
5c26335fd6 | |||
7edaeafea5 | |||
336f3f7a7b | |||
47dc3715f9 | |||
7503e05a4a | |||
b89cf1de07 | |||
be87078c25 | |||
faf352acc5 | |||
0db61dd658 | |||
ebe8ad8669 | |||
2e01f0d10e | |||
754fa1e745 | |||
8b9e0ba96b | |||
b0656aca36 | |||
623b4fee17 | |||
1b1de1dd01 | |||
968d8646b2 | |||
94eef7dceb | |||
fe647939ce | |||
984a69cb4b | |||
098a1ece68 | |||
ad6f2ad2e1 | |||
2d55252261 | |||
30ea3a1335 | |||
b7d78d1e27 | |||
3d5a645a3b | |||
4ad21e7781 | |||
b99a0c3ca2 | |||
e1842f6b80 | |||
0781a3835d | |||
98a99f0215 | |||
681b086de0 | |||
cdcc0b39e2 | |||
8eb68ba817 | |||
8d1ae4ea08 | |||
9c8ea027ef | |||
aaa56d3354 | |||
b45c49d3a4 | |||
5b3202cc89 | |||
5280f872dc | |||
fd61b963d5 | |||
a8937d3046 | |||
32b05047dc |
83
.github/workflows/build.yml
vendored
83
.github/workflows/build.yml
vendored
@ -9,18 +9,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
cache-dependency-path: ./go.mod
|
||||
- name: Tests
|
||||
run: |
|
||||
go test -v $(go list ./...) -tags skipCi
|
||||
@ -31,14 +32,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ go-tests ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
# cache
|
||||
- uses: c-hive/gha-yarn-cache@v2
|
||||
with:
|
||||
directory: ./web
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
- run: yarn install && CI=false yarn run build
|
||||
working-directory: ./web
|
||||
|
||||
@ -47,10 +46,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ go-tests ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
cache-dependency-path: ./go.mod
|
||||
- run: go version
|
||||
- name: Build
|
||||
run: |
|
||||
@ -63,9 +63,10 @@ jobs:
|
||||
needs: [ go-tests ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
cache: false
|
||||
|
||||
# gen a dummy config file
|
||||
- run: touch dummy.yml
|
||||
@ -82,35 +83,35 @@ jobs:
|
||||
needs: [ go-tests ]
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
- name: back start
|
||||
cache-dependency-path: ./go.mod
|
||||
- name: start backend
|
||||
run: nohup go run ./main.go &
|
||||
working-directory: ./
|
||||
- name: front install
|
||||
run: yarn install
|
||||
working-directory: ./web
|
||||
- name: front start
|
||||
run: nohup yarn start &
|
||||
working-directory: ./web
|
||||
- uses: cypress-io/github-action@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
working-directory: ./web
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
- run: yarn install
|
||||
working-directory: ./web
|
||||
- uses: cypress-io/github-action@v5
|
||||
with:
|
||||
start: yarn start
|
||||
wait-on: 'http://localhost:7001'
|
||||
wait-on-timeout: 180
|
||||
working-directory: ./web
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
@ -130,11 +131,11 @@ jobs:
|
||||
needs: [ frontend, backend, linter, e2e ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: -1
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
FROM node:16.13.0 AS FRONT
|
||||
FROM node:16.18.0 AS FRONT
|
||||
WORKDIR /web
|
||||
COPY ./web .
|
||||
RUN yarn config set registry https://registry.npmmirror.com
|
||||
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
||||
|
||||
|
||||
FROM golang:1.17.5 AS BACK
|
||||
FROM golang:1.19.9 AS BACK
|
||||
WORKDIR /go/src/casdoor
|
||||
COPY . .
|
||||
RUN ./build.sh
|
||||
@ -64,6 +64,7 @@ COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||
COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
||||
COPY --from=FRONT /web/build ./web/build
|
||||
RUN mkdir tempFiles
|
||||
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
CMD ["/docker-entrypoint.sh"]
|
||||
|
141
ai/ai.go
Normal file
141
ai/ai.go
Normal file
@ -0,0 +1,141 @@
|
||||
// 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 ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
func queryAnswer(authToken string, question string, timeout int) (string, error) {
|
||||
// fmt.Printf("Question: %s\n", question)
|
||||
|
||||
client := getProxyClientFromToken(authToken)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(2+timeout*2)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := client.CreateChatCompletion(
|
||||
ctx,
|
||||
openai.ChatCompletionRequest{
|
||||
Model: openai.GPT3Dot5Turbo,
|
||||
Messages: []openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: openai.ChatMessageRoleUser,
|
||||
Content: question,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
res := resp.Choices[0].Message.Content
|
||||
res = strings.Trim(res, "\n")
|
||||
// fmt.Printf("Answer: %s\n\n", res)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func QueryAnswerSafe(authToken string, question string) string {
|
||||
var res string
|
||||
var err error
|
||||
for i := 0; i < 10; i++ {
|
||||
res, err = queryAnswer(authToken, question, i)
|
||||
if err != nil {
|
||||
if i > 0 {
|
||||
fmt.Printf("\tFailed (%d): %s\n", i+1, err.Error())
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func QueryAnswerStream(authToken string, question string, writer io.Writer, builder *strings.Builder) error {
|
||||
client := getProxyClientFromToken(authToken)
|
||||
|
||||
ctx := context.Background()
|
||||
flusher, ok := writer.(http.Flusher)
|
||||
if !ok {
|
||||
return fmt.Errorf("writer does not implement http.Flusher")
|
||||
}
|
||||
// https://platform.openai.com/tokenizer
|
||||
// https://github.com/pkoukk/tiktoken-go#available-encodings
|
||||
promptTokens, err := getTokenSize(openai.GPT3TextDavinci003, question)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// https://platform.openai.com/docs/models/gpt-3-5
|
||||
maxTokens := 4097 - promptTokens
|
||||
|
||||
respStream, err := client.CreateCompletionStream(
|
||||
ctx,
|
||||
openai.CompletionRequest{
|
||||
Model: openai.GPT3TextDavinci003,
|
||||
Prompt: question,
|
||||
MaxTokens: maxTokens,
|
||||
Stream: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer respStream.Close()
|
||||
|
||||
isLeadingReturn := true
|
||||
for {
|
||||
completion, streamErr := respStream.Recv()
|
||||
if streamErr != nil {
|
||||
if streamErr == io.EOF {
|
||||
break
|
||||
}
|
||||
return streamErr
|
||||
}
|
||||
|
||||
data := completion.Choices[0].Text
|
||||
if isLeadingReturn && len(data) != 0 {
|
||||
if strings.Count(data, "\n") == len(data) {
|
||||
continue
|
||||
} else {
|
||||
isLeadingReturn = false
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s", data)
|
||||
|
||||
// Write the streamed data as Server-Sent Events
|
||||
if _, err = fmt.Fprintf(writer, "data: %s\n\n", data); err != nil {
|
||||
return err
|
||||
}
|
||||
flusher.Flush()
|
||||
// Append the response to the strings.Builder
|
||||
builder.WriteString(data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
42
ai/ai_test.go
Normal file
42
ai/ai_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
// 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.
|
||||
|
||||
//go:build !skipCi
|
||||
// +build !skipCi
|
||||
|
||||
package ai
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
object.InitConfig()
|
||||
proxy.InitHttpClient()
|
||||
|
||||
text, err := queryAnswer("", "hi", 5)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
println(text)
|
||||
}
|
||||
|
||||
func TestToken(t *testing.T) {
|
||||
println(getTokenSize(openai.GPT3TextDavinci003, ""))
|
||||
}
|
28
ai/proxy.go
Normal file
28
ai/proxy.go
Normal file
@ -0,0 +1,28 @@
|
||||
// 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 ai
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
func getProxyClientFromToken(authToken string) *openai.Client {
|
||||
config := openai.DefaultConfig(authToken)
|
||||
config.HTTPClient = proxy.ProxyHttpClient
|
||||
|
||||
c := openai.NewClientWithConfig(config)
|
||||
return c
|
||||
}
|
28
ai/util.go
Normal file
28
ai/util.go
Normal file
@ -0,0 +1,28 @@
|
||||
// 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 ai
|
||||
|
||||
import "github.com/pkoukk/tiktoken-go"
|
||||
|
||||
func getTokenSize(model string, prompt string) (int, error) {
|
||||
tkm, err := tiktoken.EncodingForModel(model)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
token := tkm.Encode(prompt, nil, nil)
|
||||
res := len(token)
|
||||
return res, nil
|
||||
}
|
@ -15,13 +15,13 @@
|
||||
package authz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
||||
)
|
||||
@ -88,8 +88,10 @@ p, *, *, GET, /api/logout, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, GET, /api/user, *, *
|
||||
p, *, *, GET, /api/health, *, *
|
||||
p, *, *, POST, /api/webhook, *, *
|
||||
p, *, *, GET, /api/get-webhook-event, *, *
|
||||
p, *, *, GET, /api/get-captcha-status, *, *
|
||||
p, *, *, *, /api/login/oauth, *, *
|
||||
p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-organization-applications, *, *
|
||||
@ -108,6 +110,7 @@ p, *, *, POST, /api/set-password, *, *
|
||||
p, *, *, POST, /api/send-verification-code, *, *
|
||||
p, *, *, GET, /api/get-captcha, *, *
|
||||
p, *, *, POST, /api/verify-captcha, *, *
|
||||
p, *, *, POST, /api/verify-code, *, *
|
||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||
p, *, *, POST, /api/upload-resource, *, *
|
||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||
@ -119,6 +122,8 @@ p, *, *, *, /cas, *, *
|
||||
p, *, *, *, /api/webauthn, *, *
|
||||
p, *, *, GET, /api/get-release, *, *
|
||||
p, *, *, GET, /api/get-default-application, *, *
|
||||
p, *, *, GET, /api/get-prometheus-info, *, *
|
||||
p, *, *, *, /api/metrics, *, *
|
||||
`
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
@ -145,9 +150,8 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
||||
}
|
||||
}
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", subOwner, subName)
|
||||
user := object.GetUser(userId)
|
||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin" && subOwner == objName)) {
|
||||
user := object.GetUser(util.GetId(subOwner, subName))
|
||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -20,5 +20,4 @@ staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
ldapServerPort = 389
|
||||
languages = en,zh,es,fr,de,id,ja,ko,ru,vi
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
|
@ -112,13 +112,8 @@ func GetLanguage(language string) string {
|
||||
|
||||
if len(language) < 2 {
|
||||
return "en"
|
||||
}
|
||||
|
||||
language = language[0:2]
|
||||
if strings.Contains(GetConfigString("languages"), language) {
|
||||
return language
|
||||
} else {
|
||||
return "en"
|
||||
return language[0:2]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@ -34,44 +35,6 @@ const (
|
||||
ResponseTypeCas = "cas"
|
||||
)
|
||||
|
||||
type RequestForm struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
Organization string `json:"organization"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
IdCard string `json:"idCard"`
|
||||
Region string `json:"region"`
|
||||
|
||||
Application string `json:"application"`
|
||||
ClientId string `json:"clientId"`
|
||||
Provider string `json:"provider"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
Method string `json:"method"`
|
||||
|
||||
EmailCode string `json:"emailCode"`
|
||||
PhoneCode string `json:"phoneCode"`
|
||||
CountryCode string `json:"countryCode"`
|
||||
|
||||
AutoSignin bool `json:"autoSignin"`
|
||||
|
||||
RelayState string `json:"relayState"`
|
||||
SamlRequest string `json:"samlRequest"`
|
||||
SamlResponse string `json:"samlResponse"`
|
||||
|
||||
CaptchaType string `json:"captchaType"`
|
||||
CaptchaToken string `json:"captchaToken"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
@ -108,28 +71,28 @@ func (c *ApiController) Signup() {
|
||||
return
|
||||
}
|
||||
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if !application.EnableSignUp {
|
||||
c.ResponseError(c.T("account:The application does not allow to sign up new account"))
|
||||
return
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
|
||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.CountryCode, form.Affiliation, c.GetAcceptLanguage())
|
||||
organization := object.GetOrganization(util.GetId("admin", authForm.Organization))
|
||||
msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" {
|
||||
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode, c.GetAcceptLanguage())
|
||||
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && authForm.Email != "" {
|
||||
checkResult := object.CheckVerificationCode(authForm.Email, authForm.EmailCode, c.GetAcceptLanguage())
|
||||
if checkResult.Code != object.VerificationSuccess {
|
||||
c.ResponseError(checkResult.Msg)
|
||||
return
|
||||
@ -137,9 +100,9 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
|
||||
var checkPhone string
|
||||
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
|
||||
checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode)
|
||||
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
|
||||
if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && authForm.Phone != "" {
|
||||
checkPhone, _ = util.GetE164Number(authForm.Phone, authForm.CountryCode)
|
||||
checkResult := object.CheckVerificationCode(checkPhone, authForm.PhoneCode, c.GetAcceptLanguage())
|
||||
if checkResult.Code != object.VerificationSuccess {
|
||||
c.ResponseError(checkResult.Msg)
|
||||
return
|
||||
@ -148,7 +111,7 @@ func (c *ApiController) Signup() {
|
||||
|
||||
id := util.GenerateId()
|
||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||
lastUser := object.GetLastUser(form.Organization)
|
||||
lastUser := object.GetLastUser(authForm.Organization)
|
||||
|
||||
lastIdInt := -1
|
||||
if lastUser != nil {
|
||||
@ -158,33 +121,33 @@ func (c *ApiController) Signup() {
|
||||
id = strconv.Itoa(lastIdInt + 1)
|
||||
}
|
||||
|
||||
username := form.Username
|
||||
username := authForm.Username
|
||||
if !application.IsSignupItemVisible("Username") {
|
||||
username = id
|
||||
}
|
||||
|
||||
initScore, err := getInitScore(organization)
|
||||
initScore, err := organization.GetInitScore()
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||
return
|
||||
}
|
||||
|
||||
user := &object.User{
|
||||
Owner: form.Organization,
|
||||
Owner: authForm.Organization,
|
||||
Name: username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: id,
|
||||
Type: "normal-user",
|
||||
Password: form.Password,
|
||||
DisplayName: form.Name,
|
||||
Password: authForm.Password,
|
||||
DisplayName: authForm.Name,
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: form.Email,
|
||||
Phone: form.Phone,
|
||||
CountryCode: form.CountryCode,
|
||||
Email: authForm.Email,
|
||||
Phone: authForm.Phone,
|
||||
CountryCode: authForm.CountryCode,
|
||||
Address: []string{},
|
||||
Affiliation: form.Affiliation,
|
||||
IdCard: form.IdCard,
|
||||
Region: form.Region,
|
||||
Affiliation: authForm.Affiliation,
|
||||
IdCard: authForm.IdCard,
|
||||
Region: authForm.Region,
|
||||
Score: initScore,
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
@ -203,10 +166,10 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
|
||||
if application.GetSignupItemRule("Display name") == "First, last" {
|
||||
if form.FirstName != "" || form.LastName != "" {
|
||||
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
|
||||
user.FirstName = form.FirstName
|
||||
user.LastName = form.LastName
|
||||
if authForm.FirstName != "" || authForm.LastName != "" {
|
||||
user.DisplayName = fmt.Sprintf("%s %s", authForm.FirstName, authForm.LastName)
|
||||
user.FirstName = authForm.FirstName
|
||||
user.LastName = authForm.LastName
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +186,7 @@ func (c *ApiController) Signup() {
|
||||
c.SetSessionUsername(user.GetId())
|
||||
}
|
||||
|
||||
object.DisableVerificationCode(form.Email)
|
||||
object.DisableVerificationCode(authForm.Email)
|
||||
object.DisableVerificationCode(checkPhone)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
|
@ -24,10 +24,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/captcha"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
@ -56,7 +56,7 @@ func tokenToResponse(token *object.Token) *Response {
|
||||
}
|
||||
|
||||
// HandleLoggedIn ...
|
||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
|
||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
|
||||
userId := user.GetId()
|
||||
|
||||
allowed, err := object.CheckAccessPermission(userId, application)
|
||||
@ -69,6 +69,12 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
return
|
||||
}
|
||||
|
||||
if form.Password != "" && user.IsMfaEnabled() {
|
||||
c.setMfaSessionData(&object.MfaSessionData{UserId: userId})
|
||||
resp = &Response{Status: object.NextMfa, Data: user.GetPreferMfa(true)}
|
||||
return
|
||||
}
|
||||
|
||||
if form.Type == ResponseTypeLogin {
|
||||
c.SetSessionUsername(userId)
|
||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||
@ -132,14 +138,10 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
|
||||
// if user did not check auto signin
|
||||
if resp.Status == "ok" && !form.AutoSignin {
|
||||
timestamp := time.Now().Unix()
|
||||
timestamp += 3600 * 24
|
||||
c.SetSessionData(&SessionData{
|
||||
ExpireTime: timestamp,
|
||||
})
|
||||
c.setExpireForSession()
|
||||
}
|
||||
|
||||
if resp.Status == "ok" && user.Owner == object.CasdoorOrganization && application.Name == object.CasdoorApplication {
|
||||
if resp.Status == "ok" {
|
||||
object.AddSession(&object.Session{
|
||||
Owner: user.Owner,
|
||||
Name: user.Name,
|
||||
@ -221,21 +223,21 @@ func isProxyProviderType(providerType string) bool {
|
||||
// @Param nonce query string false nonce
|
||||
// @Param code_challenge_method query string false code_challenge_method
|
||||
// @Param code_challenge query string false code_challenge
|
||||
// @Param form body controllers.RequestForm true "Login information"
|
||||
// @Param form body controllers.AuthForm true "Login information"
|
||||
// @Success 200 {object} Response The Response object
|
||||
// @router /login [post]
|
||||
func (c *ApiController) Login() {
|
||||
resp := &Response{}
|
||||
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if form.Username != "" {
|
||||
if form.Type == ResponseTypeLogin {
|
||||
if authForm.Username != "" {
|
||||
if authForm.Type == ResponseTypeLogin {
|
||||
if c.GetSessionUsername() != "" {
|
||||
c.ResponseError(c.T("account:Please sign out first"), c.GetSessionUsername())
|
||||
return
|
||||
@ -245,43 +247,25 @@ func (c *ApiController) Login() {
|
||||
var user *object.User
|
||||
var msg string
|
||||
|
||||
if form.Password == "" {
|
||||
var verificationCodeType string
|
||||
var checkResult string
|
||||
|
||||
if form.Name != "" {
|
||||
user = object.GetUserByFields(form.Organization, form.Name)
|
||||
}
|
||||
|
||||
// check result through Email or Phone
|
||||
var checkDest string
|
||||
if strings.Contains(form.Username, "@") {
|
||||
verificationCodeType = "email"
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
|
||||
form.Username = user.Email
|
||||
}
|
||||
checkDest = form.Username
|
||||
} else {
|
||||
verificationCodeType = "phone"
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
|
||||
form.Username = user.Phone
|
||||
}
|
||||
}
|
||||
|
||||
if user = object.GetUserByFields(form.Organization, form.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
|
||||
if authForm.Password == "" {
|
||||
if user = object.GetUserByFields(authForm.Organization, authForm.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username)))
|
||||
return
|
||||
}
|
||||
if verificationCodeType == "phone" {
|
||||
form.CountryCode = user.GetCountryCode(form.CountryCode)
|
||||
|
||||
verificationCodeType := object.GetVerifyType(authForm.Username)
|
||||
var checkDest string
|
||||
if verificationCodeType == object.VerifyTypePhone {
|
||||
authForm.CountryCode = user.GetCountryCode(authForm.CountryCode)
|
||||
var ok bool
|
||||
if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), form.CountryCode))
|
||||
if checkDest, ok = util.GetE164Number(authForm.Username, authForm.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
checkResult = object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
|
||||
// check result through Email or Phone
|
||||
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
||||
return
|
||||
@ -290,18 +274,18 @@ func (c *ApiController) Login() {
|
||||
// disable the verification code
|
||||
object.DisableVerificationCode(checkDest)
|
||||
} else {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
if !application.EnablePassword {
|
||||
c.ResponseError(c.T("auth:The login method: login with password is not enabled for the application"))
|
||||
return
|
||||
}
|
||||
|
||||
if object.CheckToEnableCaptcha(application) {
|
||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)
|
||||
var enableCaptcha bool
|
||||
if enableCaptcha = object.CheckToEnableCaptcha(application, authForm.Organization, authForm.Username); enableCaptcha {
|
||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -313,41 +297,46 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
|
||||
password := form.Password
|
||||
user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage())
|
||||
password := authForm.Password
|
||||
user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
resp = &Response{Status: "error", Msg: msg}
|
||||
} else {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
organization := object.GetOrganizationByUser(user)
|
||||
if user != nil && organization.HasRequiredMfa() && !user.IsMfaEnabled() {
|
||||
resp.Msg = object.RequiredMfa
|
||||
}
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
}
|
||||
} else if form.Provider != "" {
|
||||
} else if authForm.Provider != "" {
|
||||
var application *object.Application
|
||||
if form.ClientId != "" {
|
||||
application = object.GetApplicationByClientId(form.ClientId)
|
||||
if authForm.ClientId != "" {
|
||||
application = object.GetApplicationByClientId(authForm.ClientId)
|
||||
} else {
|
||||
application = object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
|
||||
provider := object.GetProvider(util.GetId("admin", form.Provider))
|
||||
organization := object.GetOrganization(util.GetId("admin", application.Organization))
|
||||
provider := object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||
providerItem := application.GetProviderItem(provider.Name)
|
||||
if !providerItem.IsProviderVisible() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
|
||||
@ -357,7 +346,7 @@ func (c *ApiController) Login() {
|
||||
userInfo := &idp.UserInfo{}
|
||||
if provider.Category == "SAML" {
|
||||
// SAML
|
||||
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider.Type)
|
||||
userInfo.Id, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -372,7 +361,7 @@ func (c *ApiController) Login() {
|
||||
clientSecret = provider.ClientSecret2
|
||||
}
|
||||
|
||||
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
|
||||
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, authForm.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
|
||||
if idProvider == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
|
||||
return
|
||||
@ -380,13 +369,13 @@ func (c *ApiController) Login() {
|
||||
|
||||
setHttpClient(idProvider, provider.Type)
|
||||
|
||||
if form.State != conf.GetConfigString("authState") && form.State != application.Name {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:State expected: %s, but got: %s"), conf.GetConfigString("authState"), form.State))
|
||||
if authForm.State != conf.GetConfigString("authState") && authForm.State != application.Name {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:State expected: %s, but got: %s"), conf.GetConfigString("authState"), authForm.State))
|
||||
return
|
||||
}
|
||||
|
||||
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
|
||||
token, err := idProvider.GetToken(form.Code)
|
||||
token, err := idProvider.GetToken(authForm.Code)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -404,10 +393,10 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
|
||||
if form.Method == "signup" {
|
||||
if authForm.Method == "signup" {
|
||||
user := &object.User{}
|
||||
if provider.Category == "SAML" {
|
||||
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
||||
user = object.GetUser(util.GetId(application.Organization, userInfo.Id))
|
||||
} else if provider.Category == "OAuth" {
|
||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
}
|
||||
@ -419,7 +408,7 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
|
||||
}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
@ -427,24 +416,31 @@ func (c *ApiController) Login() {
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
} else if provider.Category == "OAuth" {
|
||||
// Sign up via OAuth
|
||||
if !application.EnableSignUp {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support"), provider.Type, userInfo.Username, userInfo.DisplayName))
|
||||
return
|
||||
}
|
||||
|
||||
if !providerItem.CanSignUp {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up"), provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type))
|
||||
return
|
||||
}
|
||||
|
||||
if application.EnableLinkWithEmail {
|
||||
// find user that has the same email
|
||||
user = object.GetUserByField(application.Organization, "email", userInfo.Email)
|
||||
if userInfo.Email != "" {
|
||||
// Find existing user with Email
|
||||
user = object.GetUserByField(application.Organization, "email", userInfo.Email)
|
||||
}
|
||||
|
||||
if user == nil && userInfo.Phone != "" {
|
||||
// Find existing user with phone number
|
||||
user = object.GetUserByField(application.Organization, "phone", userInfo.Phone)
|
||||
}
|
||||
}
|
||||
|
||||
if user == nil || user.IsDeleted {
|
||||
if !application.EnableSignUp {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support"), provider.Type, userInfo.Username, userInfo.DisplayName))
|
||||
return
|
||||
}
|
||||
|
||||
if !providerItem.CanSignUp {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up"), provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type))
|
||||
return
|
||||
}
|
||||
|
||||
// Handle username conflicts
|
||||
tmpUser := object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Username))
|
||||
tmpUser := object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||
if tmpUser != nil {
|
||||
uid, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
@ -458,7 +454,7 @@ func (c *ApiController) Login() {
|
||||
|
||||
properties := map[string]string{}
|
||||
properties["no"] = strconv.Itoa(object.GetUserCount(application.Organization, "", "") + 2)
|
||||
initScore, err := getInitScore(organization)
|
||||
initScore, err := organization.GetInitScore()
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||
return
|
||||
@ -474,6 +470,9 @@ func (c *ApiController) Login() {
|
||||
Avatar: userInfo.AvatarUrl,
|
||||
Address: []string{},
|
||||
Email: userInfo.Email,
|
||||
Phone: userInfo.Phone,
|
||||
CountryCode: userInfo.CountryCode,
|
||||
Region: userInfo.CountryCode,
|
||||
Score: initScore,
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
@ -494,7 +493,7 @@ func (c *ApiController) Login() {
|
||||
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
@ -510,7 +509,7 @@ func (c *ApiController) Login() {
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))}
|
||||
}
|
||||
// resp = &Response{Status: "ok", Msg: "", Data: res}
|
||||
} else { // form.Method != "signup"
|
||||
} else { // authForm.Method != "signup"
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id)), userInfo)
|
||||
@ -535,24 +534,56 @@ func (c *ApiController) Login() {
|
||||
resp = &Response{Status: "error", Msg: "Failed to link user account", Data: isLinked}
|
||||
}
|
||||
}
|
||||
} else if c.getMfaSessionData() != nil {
|
||||
mfaSession := c.getMfaSessionData()
|
||||
user := object.GetUser(mfaSession.UserId)
|
||||
|
||||
if authForm.Passcode != "" {
|
||||
MfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferMfa(false))
|
||||
err = MfaUtil.Verify(authForm.Passcode)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if authForm.RecoveryCode != "" {
|
||||
err = object.RecoverTfs(user, authForm.RecoveryCode)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
} else {
|
||||
if c.GetSessionUsername() != "" {
|
||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:Unknown authentication type (not password or provider), form = %s"), util.StructToJson(form)))
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:Unknown authentication type (not password or provider), form = %s"), util.StructToJson(authForm)))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -564,7 +595,7 @@ func (c *ApiController) Login() {
|
||||
func (c *ApiController) GetSamlLogin() {
|
||||
providerId := c.Input().Get("id")
|
||||
relayState := c.Input().Get("relayState")
|
||||
authURL, method, err := object.GenerateSamlLoginUrl(providerId, relayState, c.GetAcceptLanguage())
|
||||
authURL, method, err := object.GenerateSamlRequest(providerId, relayState, c.Ctx.Request.Host, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
@ -628,3 +659,21 @@ func (c *ApiController) GetWebhookEventType() {
|
||||
wechatScanType = ""
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetCaptchaStatus
|
||||
// @Title GetCaptchaStatus
|
||||
// @Tag Token API
|
||||
// @Description Get Login Error Counts
|
||||
// @Param id query string true "The id ( owner/name ) of user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/get-captcha-status [get]
|
||||
func (c *ApiController) GetCaptchaStatus() {
|
||||
organization := c.Input().Get("organization")
|
||||
userId := c.Input().Get("user_id")
|
||||
user := object.GetUserByFields(organization, userId)
|
||||
var captchaEnabled bool
|
||||
if user != nil && user.SigninWrongTimes >= object.SigninWrongTimesLimit {
|
||||
captchaEnabled = true
|
||||
}
|
||||
c.ResponseOk(captchaEnabled)
|
||||
}
|
||||
|
@ -41,18 +41,44 @@ type SessionData struct {
|
||||
}
|
||||
|
||||
func (c *ApiController) IsGlobalAdmin() bool {
|
||||
username := c.GetSessionUsername()
|
||||
if strings.HasPrefix(username, "app/") {
|
||||
// e.g., "app/app-casnode"
|
||||
return true
|
||||
}
|
||||
isGlobalAdmin, _ := c.isGlobalAdmin()
|
||||
|
||||
user := object.GetUser(username)
|
||||
if user == nil {
|
||||
return isGlobalAdmin
|
||||
}
|
||||
|
||||
func (c *ApiController) IsAdmin() bool {
|
||||
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||
if !isGlobalAdmin && user == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return user.Owner == "built-in" || user.IsGlobalAdmin
|
||||
return isGlobalAdmin || user.IsAdmin
|
||||
}
|
||||
|
||||
func (c *ApiController) isGlobalAdmin() (bool, *object.User) {
|
||||
username := c.GetSessionUsername()
|
||||
if strings.HasPrefix(username, "app/") {
|
||||
// e.g., "app/app-casnode"
|
||||
return true, nil
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
if user == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return user.Owner == "built-in" || user.IsGlobalAdmin, user
|
||||
}
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
user = nil
|
||||
} else {
|
||||
user = object.GetUser(userId)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// GetSessionUsername ...
|
||||
@ -142,6 +168,30 @@ func (c *ApiController) SetSessionData(s *SessionData) {
|
||||
c.SetSession("SessionData", util.StructToJson(s))
|
||||
}
|
||||
|
||||
func (c *ApiController) setMfaSessionData(data *object.MfaSessionData) {
|
||||
c.SetSession(object.MfaSessionUserId, data.UserId)
|
||||
}
|
||||
|
||||
func (c *ApiController) getMfaSessionData() *object.MfaSessionData {
|
||||
userId := c.GetSession(object.MfaSessionUserId)
|
||||
if userId == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := &object.MfaSessionData{
|
||||
UserId: userId.(string),
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (c *ApiController) setExpireForSession() {
|
||||
timestamp := time.Now().Unix()
|
||||
timestamp += 3600 * 24
|
||||
c.SetSessionData(&SessionData{
|
||||
ExpireTime: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
func wrapActionResponse(affected bool) *Response {
|
||||
if affected {
|
||||
return &Response{Status: "ok", Msg: "", Data: "Affected"}
|
||||
@ -157,3 +207,14 @@ func wrapErrorResponse(err error) *Response {
|
||||
return &Response{Status: "error", Msg: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) Finish() {
|
||||
if strings.HasPrefix(c.Ctx.Input.URL(), "/api") {
|
||||
startTime := c.Ctx.Input.GetData("startTime")
|
||||
if startTime != nil {
|
||||
latency := time.Since(startTime.(time.Time)).Milliseconds()
|
||||
object.ApiLatency.WithLabelValues(c.Ctx.Input.URL(), c.Ctx.Input.Method()).Observe(float64(latency))
|
||||
}
|
||||
}
|
||||
c.Controller.Finish()
|
||||
}
|
||||
|
@ -72,6 +72,11 @@ func (c *RootController) CasProxyValidate() {
|
||||
c.CasP3ServiceAndProxyValidate()
|
||||
}
|
||||
|
||||
func queryUnescape(service string) string {
|
||||
s, _ := url.QueryUnescape(service)
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *RootController) CasP3ServiceAndProxyValidate() {
|
||||
ticket := c.Input().Get("ticket")
|
||||
format := c.Input().Get("format")
|
||||
@ -91,7 +96,7 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
|
||||
// find the token
|
||||
if ok {
|
||||
// check whether service is the one for which we previously issued token
|
||||
if strings.HasPrefix(service, issuedService) {
|
||||
if strings.HasPrefix(service, issuedService) || strings.HasPrefix(queryUnescape(service), issuedService) {
|
||||
serviceResponse.Success = response
|
||||
} else {
|
||||
// service not match
|
||||
|
@ -31,13 +31,14 @@ func (c *ApiController) GetCasbinAdapters() {
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
organization := c.Input().Get("organization")
|
||||
if limit == "" || page == "" {
|
||||
adapters := object.GetCasbinAdapters(owner)
|
||||
adapters := object.GetCasbinAdapters(owner, organization)
|
||||
c.ResponseOk(adapters)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, field, value)))
|
||||
adapters := object.GetPaginationCasbinAdapters(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, organization, field, value)))
|
||||
adapters := object.GetPaginationCasbinAdapters(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(adapters, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,30 @@ func (c *ApiController) GetCerts() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobleCerts
|
||||
// @Title GetGlobleCerts
|
||||
// @Tag Cert API
|
||||
// @Description get globle certs
|
||||
// @Success 200 {array} object.Cert The Response object
|
||||
// @router /get-globle-certs [get]
|
||||
func (c *ApiController) GetGlobleCerts() {
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetMaskedCerts(object.GetGlobleCerts())
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalCertsCount(field, value)))
|
||||
certs := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(certs, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetCert
|
||||
// @Title GetCert
|
||||
// @Tag Cert API
|
||||
|
124
controllers/chat.go
Normal file
124
controllers/chat.go
Normal file
@ -0,0 +1,124 @@
|
||||
// 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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetChats
|
||||
// @Title GetChats
|
||||
// @Tag Chat API
|
||||
// @Description get chats
|
||||
// @Param owner query string true "The owner of chats"
|
||||
// @Success 200 {array} object.Chat The Response object
|
||||
// @router /get-chats [get]
|
||||
func (c *ApiController) GetChats() {
|
||||
owner := c.Input().Get("owner")
|
||||
owner = "admin"
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetMaskedChats(object.GetChats(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetChatCount(owner, field, value)))
|
||||
chats := object.GetMaskedChats(object.GetPaginationChats(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(chats, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetChat
|
||||
// @Title GetChat
|
||||
// @Tag Chat API
|
||||
// @Description get chat
|
||||
// @Param id query string true "The id ( owner/name ) of the chat"
|
||||
// @Success 200 {object} object.Chat The Response object
|
||||
// @router /get-chat [get]
|
||||
func (c *ApiController) GetChat() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetMaskedChat(object.GetChat(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateChat
|
||||
// @Title UpdateChat
|
||||
// @Tag Chat API
|
||||
// @Description update chat
|
||||
// @Param id query string true "The id ( owner/name ) of the chat"
|
||||
// @Param body body object.Chat true "The details of the chat"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-chat [post]
|
||||
func (c *ApiController) UpdateChat() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var chat object.Chat
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateChat(id, &chat))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddChat
|
||||
// @Title AddChat
|
||||
// @Tag Chat API
|
||||
// @Description add chat
|
||||
// @Param body body object.Chat true "The details of the chat"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-chat [post]
|
||||
func (c *ApiController) AddChat() {
|
||||
var chat object.Chat
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddChat(&chat))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteChat
|
||||
// @Title DeleteChat
|
||||
// @Tag Chat API
|
||||
// @Description delete chat
|
||||
// @Param body body object.Chat true "The details of the chat"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-chat [post]
|
||||
func (c *ApiController) DeleteChat() {
|
||||
var chat object.Chat
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteChat(&chat))
|
||||
c.ServeJSON()
|
||||
}
|
@ -18,30 +18,69 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (c *ApiController) Enforce() {
|
||||
var permissionRule object.PermissionRule
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRule)
|
||||
permissionId := c.Input().Get("permissionId")
|
||||
modelId := c.Input().Get("modelId")
|
||||
resourceId := c.Input().Get("resourceId")
|
||||
|
||||
var request object.CasbinRequest
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.Enforce(&permissionRule)
|
||||
if permissionId != "" {
|
||||
c.Data["json"] = object.Enforce(permissionId, &request)
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
permissions := make([]*object.Permission, 0)
|
||||
res := []bool{}
|
||||
|
||||
if modelId != "" {
|
||||
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||
permissions = object.GetPermissionsByModel(owner, modelName)
|
||||
} else {
|
||||
permissions = object.GetPermissionsByResource(resourceId)
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
res = append(res, object.Enforce(permission.GetId(), &request))
|
||||
}
|
||||
c.Data["json"] = res
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) BatchEnforce() {
|
||||
var permissionRules []object.PermissionRule
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRules)
|
||||
permissionId := c.Input().Get("permissionId")
|
||||
modelId := c.Input().Get("modelId")
|
||||
|
||||
var requests []object.CasbinRequest
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.BatchEnforce(permissionRules)
|
||||
c.ServeJSON()
|
||||
if permissionId != "" {
|
||||
c.Data["json"] = object.BatchEnforce(permissionId, &requests)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||
permissions := object.GetPermissionsByModel(owner, modelName)
|
||||
|
||||
res := [][]bool{}
|
||||
for _, permission := range permissions {
|
||||
res = append(res, object.BatchEnforce(permission.GetId(), &requests))
|
||||
}
|
||||
c.Data["json"] = res
|
||||
c.ServeJSON()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllObjects() {
|
||||
|
@ -23,7 +23,8 @@ import (
|
||||
|
||||
type LdapResp struct {
|
||||
// Groups []LdapRespGroup `json:"groups"`
|
||||
Users []object.LdapRespUser `json:"users"`
|
||||
Users []object.LdapUser `json:"users"`
|
||||
ExistUuids []string `json:"existUuids"`
|
||||
}
|
||||
|
||||
//type LdapRespGroup struct {
|
||||
@ -32,8 +33,8 @@ type LdapResp struct {
|
||||
//}
|
||||
|
||||
type LdapSyncResp struct {
|
||||
Exist []object.LdapRespUser `json:"exist"`
|
||||
Failed []object.LdapRespUser `json:"failed"`
|
||||
Exist []object.LdapUser `json:"exist"`
|
||||
Failed []object.LdapUser `json:"failed"`
|
||||
}
|
||||
|
||||
// GetLdapUsers
|
||||
@ -65,32 +66,23 @@ func (c *ApiController) GetLdapUsers() {
|
||||
// })
|
||||
//}
|
||||
|
||||
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
|
||||
users, err := conn.GetLdapUsers(ldapServer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var resp LdapResp
|
||||
uuids := make([]string, len(users))
|
||||
for _, user := range users {
|
||||
resp.Users = append(resp.Users, object.LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
// GroupName: groupsMap[user.GidNumber].Cn,
|
||||
Uuid: user.Uuid,
|
||||
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
||||
})
|
||||
uuids = append(uuids, user.Uuid)
|
||||
for i, user := range users {
|
||||
uuids[i] = user.GetLdapUuid()
|
||||
}
|
||||
existUuids := object.GetExistUuids(ldapServer.Owner, uuids)
|
||||
|
||||
existUuids := object.CheckLdapUuidExist(ldapServer.Owner, uuids)
|
||||
|
||||
c.ResponseOk(resp, existUuids)
|
||||
resp := LdapResp{
|
||||
Users: object.AutoAdjustLdapUser(users),
|
||||
ExistUuids: existUuids,
|
||||
}
|
||||
c.ResponseOk(resp)
|
||||
}
|
||||
|
||||
// GetLdaps
|
||||
@ -131,7 +123,7 @@ func (c *ApiController) AddLdap() {
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
@ -160,7 +152,7 @@ func (c *ApiController) AddLdap() {
|
||||
func (c *ApiController) UpdateLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
@ -205,7 +197,7 @@ func (c *ApiController) DeleteLdap() {
|
||||
func (c *ApiController) SyncLdapUsers() {
|
||||
owner := c.Input().Get("owner")
|
||||
ldapId := c.Input().Get("ldapId")
|
||||
var users []object.LdapRespUser
|
||||
var users []object.LdapUser
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
@ -214,10 +206,10 @@ func (c *ApiController) SyncLdapUsers() {
|
||||
|
||||
object.UpdateLdapSyncTime(ldapId)
|
||||
|
||||
exist, failed := object.SyncLdapUsers(owner, users, ldapId)
|
||||
exist, failed, _ := object.SyncLdapUsers(owner, users, ldapId)
|
||||
|
||||
c.ResponseOk(&LdapSyncResp{
|
||||
Exist: *exist,
|
||||
Failed: *failed,
|
||||
Exist: exist,
|
||||
Failed: failed,
|
||||
})
|
||||
}
|
||||
|
@ -1,138 +0,0 @@
|
||||
// Copyright 2022 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 controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/forestmgy/ldapserver"
|
||||
"github.com/lor00x/goldap/message"
|
||||
)
|
||||
|
||||
func StartLdapServer() {
|
||||
server := ldapserver.NewServer()
|
||||
routes := ldapserver.NewRouteMux()
|
||||
|
||||
routes.Bind(handleBind)
|
||||
routes.Search(handleSearch).Label(" SEARCH****")
|
||||
|
||||
server.Handle(routes)
|
||||
server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
|
||||
}
|
||||
|
||||
func handleBind(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
||||
r := m.GetBindRequest()
|
||||
res := ldapserver.NewBindResponse(ldapserver.LDAPResultSuccess)
|
||||
|
||||
if r.AuthenticationChoice() == "simple" {
|
||||
bindusername, bindorg, err := object.GetNameAndOrgFromDN(string(r.Name()))
|
||||
if err != "" {
|
||||
log.Printf("Bind failed ,ErrMsg=%s", err)
|
||||
res.SetResultCode(ldapserver.LDAPResultInvalidDNSyntax)
|
||||
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
bindpassword := string(r.AuthenticationSimple())
|
||||
binduser, err := object.CheckUserPassword(bindorg, bindusername, bindpassword, "en")
|
||||
if err != "" {
|
||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||
res.SetResultCode(ldapserver.LDAPResultInvalidCredentials)
|
||||
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
if bindorg == "built-in" {
|
||||
m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true
|
||||
} else if binduser.IsAdmin {
|
||||
m.Client.IsOrgAdmin = true
|
||||
}
|
||||
m.Client.IsAuthenticated = true
|
||||
m.Client.UserName = bindusername
|
||||
m.Client.OrgName = bindorg
|
||||
} else {
|
||||
res.SetResultCode(ldapserver.LDAPResultAuthMethodNotSupported)
|
||||
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
func handleSearch(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
||||
res := ldapserver.NewSearchResultDoneResponse(ldapserver.LDAPResultSuccess)
|
||||
if !m.Client.IsAuthenticated {
|
||||
res.SetResultCode(ldapserver.LDAPResultUnwillingToPerform)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
r := m.GetSearchRequest()
|
||||
if r.FilterString() == "(objectClass=*)" {
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
name, org, errCode := object.GetUserNameAndOrgFromBaseDnAndFilter(string(r.BaseObject()), r.FilterString())
|
||||
if errCode != ldapserver.LDAPResultSuccess {
|
||||
res.SetResultCode(errCode)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
// Handle Stop Signal (server stop / client disconnected / Abandoned request....)
|
||||
select {
|
||||
case <-m.Done:
|
||||
log.Print("Leaving handleSearch...")
|
||||
return
|
||||
default:
|
||||
}
|
||||
users, errCode := object.GetFilteredUsers(m, name, org)
|
||||
if errCode != ldapserver.LDAPResultSuccess {
|
||||
res.SetResultCode(errCode)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(users); i++ {
|
||||
user := users[i]
|
||||
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
||||
e := ldapserver.NewSearchResultEntry(dn)
|
||||
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||
e.AddAttribute("uid", message.AttributeValue(user.Name))
|
||||
e.AddAttribute("email", message.AttributeValue(user.Email))
|
||||
e.AddAttribute("mobile", message.AttributeValue(user.Phone))
|
||||
e.AddAttribute("userPassword", message.AttributeValue(getUserPasswordWithType(user)))
|
||||
// e.AddAttribute("postalAddress", message.AttributeValue(user.Address[0]))
|
||||
w.Write(e)
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
// get user password with hash type prefix
|
||||
// TODO not handle salt yet
|
||||
// @return {md5}5f4dcc3b5aa765d61d8327deb882cf99
|
||||
func getUserPasswordWithType(user *object.User) string {
|
||||
org := object.GetOrganizationByUser(user)
|
||||
if org.PasswordType == "" || org.PasswordType == "plain" {
|
||||
return user.Password
|
||||
}
|
||||
prefix := org.PasswordType
|
||||
if prefix == "salt" {
|
||||
prefix = "sha256"
|
||||
} else if prefix == "md5-salt" {
|
||||
prefix = "md5"
|
||||
} else if prefix == "pbkdf2-salt" {
|
||||
prefix = "pbkdf2"
|
||||
}
|
||||
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
||||
}
|
256
controllers/message.go
Normal file
256
controllers/message.go
Normal file
@ -0,0 +1,256 @@
|
||||
// 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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/ai"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetMessages
|
||||
// @Title GetMessages
|
||||
// @Tag Message API
|
||||
// @Description get messages
|
||||
// @Param owner query string true "The owner of messages"
|
||||
// @Success 200 {array} object.Message The Response object
|
||||
// @router /get-messages [get]
|
||||
func (c *ApiController) GetMessages() {
|
||||
owner := c.Input().Get("owner")
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
chat := c.Input().Get("chat")
|
||||
organization := c.Input().Get("organization")
|
||||
if limit == "" || page == "" {
|
||||
var messages []*object.Message
|
||||
if chat == "" {
|
||||
messages = object.GetMessages(owner)
|
||||
} else {
|
||||
messages = object.GetChatMessages(chat)
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedMessages(messages)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetMessageCount(owner, organization, field, value)))
|
||||
messages := object.GetMaskedMessages(object.GetPaginationMessages(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(messages, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetMessage
|
||||
// @Title GetMessage
|
||||
// @Tag Message API
|
||||
// @Description get message
|
||||
// @Param id query string true "The id ( owner/name ) of the message"
|
||||
// @Success 200 {object} object.Message The Response object
|
||||
// @router /get-message [get]
|
||||
func (c *ApiController) GetMessage() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetMaskedMessage(object.GetMessage(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) ResponseErrorStream(errorText string) {
|
||||
event := fmt.Sprintf("event: myerror\ndata: %s\n\n", errorText)
|
||||
_, err := c.Ctx.ResponseWriter.Write([]byte(event))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetMessageAnswer
|
||||
// @Title GetMessageAnswer
|
||||
// @Tag Message API
|
||||
// @Description get message answer
|
||||
// @Param id query string true "The id ( owner/name ) of the message"
|
||||
// @Success 200 {object} object.Message The Response object
|
||||
// @router /get-message-answer [get]
|
||||
func (c *ApiController) GetMessageAnswer() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
|
||||
c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
|
||||
c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
|
||||
|
||||
message := object.GetMessage(id)
|
||||
if message == nil {
|
||||
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The message: %s is not found"), id))
|
||||
return
|
||||
}
|
||||
|
||||
if message.Author != "AI" || message.ReplyTo == "" || message.Text != "" {
|
||||
c.ResponseErrorStream(c.T("chat:The message is invalid"))
|
||||
return
|
||||
}
|
||||
|
||||
chatId := util.GetId("admin", message.Chat)
|
||||
chat := object.GetChat(chatId)
|
||||
if chat == nil || chat.Organization != message.Organization {
|
||||
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The chat: %s is not found"), chatId))
|
||||
return
|
||||
}
|
||||
|
||||
if chat.Type != "AI" {
|
||||
c.ResponseErrorStream(c.T("chat:The chat type must be \"AI\""))
|
||||
return
|
||||
}
|
||||
|
||||
questionMessage := object.GetMessage(message.ReplyTo)
|
||||
if questionMessage == nil {
|
||||
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The message: %s is not found"), id))
|
||||
return
|
||||
}
|
||||
|
||||
providerId := util.GetId(chat.Owner, chat.User2)
|
||||
provider := object.GetProvider(providerId)
|
||||
if provider == nil {
|
||||
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The provider: %s is not found"), providerId))
|
||||
return
|
||||
}
|
||||
|
||||
if provider.Category != "AI" || provider.ClientSecret == "" {
|
||||
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The provider: %s is invalid"), providerId))
|
||||
return
|
||||
}
|
||||
|
||||
c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
|
||||
c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
|
||||
c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
|
||||
|
||||
authToken := provider.ClientSecret
|
||||
question := questionMessage.Text
|
||||
var stringBuilder strings.Builder
|
||||
|
||||
fmt.Printf("Question: [%s]\n", questionMessage.Text)
|
||||
fmt.Printf("Answer: [")
|
||||
|
||||
err := ai.QueryAnswerStream(authToken, question, c.Ctx.ResponseWriter, &stringBuilder)
|
||||
if err != nil {
|
||||
c.ResponseErrorStream(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("]\n")
|
||||
|
||||
event := fmt.Sprintf("event: end\ndata: %s\n\n", "end")
|
||||
_, err = c.Ctx.ResponseWriter.Write([]byte(event))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
answer := stringBuilder.String()
|
||||
|
||||
message.Text = answer
|
||||
object.UpdateMessage(message.GetId(), message)
|
||||
}
|
||||
|
||||
// UpdateMessage
|
||||
// @Title UpdateMessage
|
||||
// @Tag Message API
|
||||
// @Description update message
|
||||
// @Param id query string true "The id ( owner/name ) of the message"
|
||||
// @Param body body object.Message true "The details of the message"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-message [post]
|
||||
func (c *ApiController) UpdateMessage() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var message object.Message
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateMessage(id, &message))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddMessage
|
||||
// @Title AddMessage
|
||||
// @Tag Message API
|
||||
// @Description add message
|
||||
// @Param body body object.Message true "The details of the message"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-message [post]
|
||||
func (c *ApiController) AddMessage() {
|
||||
var message object.Message
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var chat *object.Chat
|
||||
if message.Chat != "" {
|
||||
chatId := util.GetId("admin", message.Chat)
|
||||
chat = object.GetChat(chatId)
|
||||
if chat == nil || chat.Organization != message.Organization {
|
||||
c.ResponseError(fmt.Sprintf(c.T("chat:The chat: %s is not found"), chatId))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
affected := object.AddMessage(&message)
|
||||
if affected {
|
||||
if chat != nil && chat.Type == "AI" {
|
||||
answerMessage := &object.Message{
|
||||
Owner: message.Owner,
|
||||
Name: fmt.Sprintf("message_%s", util.GetRandomName()),
|
||||
CreatedTime: util.GetCurrentTimeEx(message.CreatedTime),
|
||||
Organization: message.Organization,
|
||||
Chat: message.Chat,
|
||||
ReplyTo: message.GetId(),
|
||||
Author: "AI",
|
||||
Text: "",
|
||||
}
|
||||
object.AddMessage(answerMessage)
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteMessage
|
||||
// @Title DeleteMessage
|
||||
// @Tag Message API
|
||||
// @Description delete message
|
||||
// @Param body body object.Message true "The details of the message"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-message [post]
|
||||
func (c *ApiController) DeleteMessage() {
|
||||
var message object.Message
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteMessage(&message))
|
||||
c.ServeJSON()
|
||||
}
|
194
controllers/mfa.go
Normal file
194
controllers/mfa.go
Normal file
@ -0,0 +1,194 @@
|
||||
// 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 controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/beego/beego"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// MfaSetupInitiate
|
||||
// @Title MfaSetupInitiate
|
||||
// @Tag MFA API
|
||||
// @Description setup MFA
|
||||
// @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} The Response object
|
||||
// @router /mfa/setup/initiate [post]
|
||||
func (c *ApiController) MfaSetupInitiate() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
name := c.Ctx.Request.Form.Get("name")
|
||||
authType := c.Ctx.Request.Form.Get("type")
|
||||
userId := util.GetId(owner, name)
|
||||
|
||||
if len(userId) == 0 {
|
||||
c.ResponseError(http.StatusText(http.StatusBadRequest))
|
||||
return
|
||||
}
|
||||
|
||||
MfaUtil := object.GetMfaUtil(authType, nil)
|
||||
if MfaUtil == nil {
|
||||
c.ResponseError("Invalid auth type")
|
||||
}
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError("User doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
issuer := beego.AppConfig.String("appname")
|
||||
accountName := user.GetId()
|
||||
|
||||
mfaProps, err := MfaUtil.Initiate(c.Ctx, issuer, accountName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := mfaProps
|
||||
c.ResponseOk(resp)
|
||||
}
|
||||
|
||||
// MfaSetupVerify
|
||||
// @Title MfaSetupVerify
|
||||
// @Tag MFA API
|
||||
// @Description setup verify totp
|
||||
// @param secret form string true "MFA secret"
|
||||
// @param passcode form string true "MFA passcode"
|
||||
// @Success 200 {object} Response object
|
||||
// @router /mfa/setup/verify [post]
|
||||
func (c *ApiController) MfaSetupVerify() {
|
||||
authType := c.Ctx.Request.Form.Get("type")
|
||||
passcode := c.Ctx.Request.Form.Get("passcode")
|
||||
|
||||
if authType == "" || passcode == "" {
|
||||
c.ResponseError("missing auth type or passcode")
|
||||
return
|
||||
}
|
||||
MfaUtil := object.GetMfaUtil(authType, nil)
|
||||
|
||||
err := MfaUtil.SetupVerify(c.Ctx, passcode)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
} else {
|
||||
c.ResponseOk(http.StatusText(http.StatusOK))
|
||||
}
|
||||
}
|
||||
|
||||
// MfaSetupEnable
|
||||
// @Title MfaSetupEnable
|
||||
// @Tag MFA API
|
||||
// @Description enable totp
|
||||
// @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
|
||||
// @router /mfa/setup/enable [post]
|
||||
func (c *ApiController) MfaSetupEnable() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
name := c.Ctx.Request.Form.Get("name")
|
||||
authType := c.Ctx.Request.Form.Get("type")
|
||||
|
||||
user := object.GetUser(util.GetId(owner, name))
|
||||
if user == nil {
|
||||
c.ResponseError("User doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
twoFactor := object.GetMfaUtil(authType, nil)
|
||||
err := twoFactor.Enable(c.Ctx, user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(http.StatusText(http.StatusOK))
|
||||
}
|
||||
|
||||
// DeleteMfa
|
||||
// @Title DeleteMfa
|
||||
// @Tag MFA API
|
||||
// @Description: Delete MFA
|
||||
// @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
|
||||
// @router /delete-mfa/ [post]
|
||||
func (c *ApiController) DeleteMfa() {
|
||||
id := c.Ctx.Request.Form.Get("id")
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
name := c.Ctx.Request.Form.Get("name")
|
||||
userId := util.GetId(owner, name)
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError("User doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
mfaProps := user.MultiFactorAuths[:0]
|
||||
i := 0
|
||||
for _, mfaProp := range mfaProps {
|
||||
if mfaProp.Id != id {
|
||||
mfaProps[i] = mfaProp
|
||||
i++
|
||||
}
|
||||
}
|
||||
user.MultiFactorAuths = mfaProps
|
||||
object.UpdateUser(userId, user, []string{"multi_factor_auths"}, user.IsAdminUser())
|
||||
c.ResponseOk(user.MultiFactorAuths)
|
||||
}
|
||||
|
||||
// SetPreferredMfa
|
||||
// @Title SetPreferredMfa
|
||||
// @Tag MFA API
|
||||
// @Description: Set specific Mfa Preferred
|
||||
// @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
|
||||
// @router /set-preferred-mfa [post]
|
||||
func (c *ApiController) SetPreferredMfa() {
|
||||
id := c.Ctx.Request.Form.Get("id")
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
name := c.Ctx.Request.Form.Get("name")
|
||||
userId := util.GetId(owner, name)
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError("User doesn't exist")
|
||||
return
|
||||
}
|
||||
|
||||
mfaProps := user.MultiFactorAuths
|
||||
for i, mfaProp := range user.MultiFactorAuths {
|
||||
if mfaProp.Id == id {
|
||||
mfaProps[i].IsPreferred = true
|
||||
} else {
|
||||
mfaProps[i].IsPreferred = false
|
||||
}
|
||||
}
|
||||
|
||||
object.UpdateUser(userId, user, []string{"multi_factor_auths"}, user.IsAdminUser())
|
||||
|
||||
for i, mfaProp := range mfaProps {
|
||||
mfaProps[i] = object.GetMaskedProps(mfaProp)
|
||||
}
|
||||
c.ResponseOk(mfaProps)
|
||||
}
|
39
controllers/prometheus.go
Normal file
39
controllers/prometheus.go
Normal file
@ -0,0 +1,39 @@
|
||||
// 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 controllers
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// GetPrometheusInfo
|
||||
// @Title GetPrometheusInfo
|
||||
// @Tag Prometheus API
|
||||
// @Description get Prometheus Info
|
||||
// @Success 200 {object} object.PrometheusInfo The Response object
|
||||
// @router /get-prometheus-info [get]
|
||||
func (c *ApiController) GetPrometheusInfo() {
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
prometheusInfo, err := object.GetPrometheusInfo()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(prometheusInfo)
|
||||
}
|
@ -37,13 +37,19 @@ func (c *ApiController) GetProviders() {
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
|
||||
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner))
|
||||
c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner), isMaskEnabled)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner, field, value)))
|
||||
providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder), isMaskEnabled)
|
||||
c.ResponseOk(providers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@ -61,13 +67,19 @@ func (c *ApiController) GetGlobalProviders() {
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
|
||||
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetMaskedProviders(object.GetGlobalProviders())
|
||||
c.Data["json"] = object.GetMaskedProviders(object.GetGlobalProviders(), isMaskEnabled)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalProviderCount(field, value)))
|
||||
providers := object.GetMaskedProviders(object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
providers := object.GetMaskedProviders(object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder), isMaskEnabled)
|
||||
c.ResponseOk(providers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@ -81,7 +93,13 @@ func (c *ApiController) GetGlobalProviders() {
|
||||
// @router /get-provider [get]
|
||||
func (c *ApiController) GetProvider() {
|
||||
id := c.Input().Get("id")
|
||||
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id))
|
||||
|
||||
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id), isMaskEnabled)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -235,10 +236,21 @@ func (c *ApiController) UploadResource() {
|
||||
user.Avatar = fileUrl
|
||||
object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
|
||||
case "termsOfUse":
|
||||
applicationId := fmt.Sprintf("admin/%s", parent)
|
||||
app := object.GetApplication(applicationId)
|
||||
app.TermsOfUse = fileUrl
|
||||
object.UpdateApplication(applicationId, app)
|
||||
user := object.GetUserNoCheck(util.GetId(owner, username))
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(owner, username)))
|
||||
return
|
||||
}
|
||||
|
||||
if !user.IsAdminUser() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
_, applicationId := util.GetOwnerAndNameFromIdNoCheck(strings.TrimRight(fullFilePath, ".html"))
|
||||
applicationObj := object.GetApplication(applicationId)
|
||||
applicationObj.TermsOfUse = fileUrl
|
||||
object.UpdateApplication(applicationId, applicationObj)
|
||||
}
|
||||
|
||||
c.ResponseOk(fileUrl, objectKey)
|
||||
|
@ -37,13 +37,14 @@ func (c *ApiController) GetSyncers() {
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
organization := c.Input().Get("organization")
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetSyncers(owner)
|
||||
c.Data["json"] = object.GetOrganizationSyncers(owner, organization)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, field, value)))
|
||||
syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, organization, field, value)))
|
||||
syncers := object.GetPaginationSyncers(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(syncers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
@ -21,9 +21,8 @@ import (
|
||||
// GetSystemInfo
|
||||
// @Title GetSystemInfo
|
||||
// @Tag System API
|
||||
// @Description get user's system info
|
||||
// @Param id query string true "The id ( owner/name ) of the user"
|
||||
// @Success 200 {object} object.SystemInfo The Response object
|
||||
// @Description get system info like CPU and memory usage
|
||||
// @Success 200 {object} util.SystemInfo The Response object
|
||||
// @router /get-system-info [get]
|
||||
func (c *ApiController) GetSystemInfo() {
|
||||
_, ok := c.RequireAdmin()
|
||||
@ -43,8 +42,8 @@ func (c *ApiController) GetSystemInfo() {
|
||||
// GetVersionInfo
|
||||
// @Title GetVersionInfo
|
||||
// @Tag System API
|
||||
// @Description get local git repo's latest release version info
|
||||
// @Success 200 {string} local latest version hash of Casdoor
|
||||
// @Description get version info like Casdoor release version and commit ID
|
||||
// @Success 200 {object} util.VersionInfo The Response object
|
||||
// @router /get-version-info [get]
|
||||
func (c *ApiController) GetVersionInfo() {
|
||||
versionInfo, err := util.GetVersionInfo()
|
||||
@ -60,3 +59,13 @@ func (c *ApiController) GetVersionInfo() {
|
||||
|
||||
c.ResponseOk(versionInfo)
|
||||
}
|
||||
|
||||
// Health
|
||||
// @Title Health
|
||||
// @Tag System API
|
||||
// @Description check if the system is live
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /health [get]
|
||||
func (c *ApiController) Health() {
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
@ -39,13 +39,14 @@ func (c *ApiController) GetTokens() {
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
organization := c.Input().Get("organization")
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetTokens(owner)
|
||||
c.Data["json"] = object.GetTokens(owner, organization)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, field, value)))
|
||||
tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, organization, field, value)))
|
||||
tokens := object.GetPaginationTokens(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(tokens, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@ -124,40 +125,6 @@ func (c *ApiController) DeleteToken() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetOAuthCode
|
||||
// @Title GetOAuthCode
|
||||
// @Tag Token API
|
||||
// @Description get OAuth code
|
||||
// @Param id query string true "The id ( owner/name ) of user"
|
||||
// @Param client_id query string true "OAuth client id"
|
||||
// @Param response_type query string true "OAuth response type"
|
||||
// @Param redirect_uri query string true "OAuth redirect URI"
|
||||
// @Param scope query string true "OAuth scope"
|
||||
// @Param state query string true "OAuth state"
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @router /login/oauth/code [post]
|
||||
func (c *ApiController) GetOAuthCode() {
|
||||
userId := c.Input().Get("user_id")
|
||||
clientId := c.Input().Get("client_id")
|
||||
responseType := c.Input().Get("response_type")
|
||||
redirectUri := c.Input().Get("redirect_uri")
|
||||
scope := c.Input().Get("scope")
|
||||
state := c.Input().Get("state")
|
||||
nonce := c.Input().Get("nonce")
|
||||
|
||||
challengeMethod := c.Input().Get("code_challenge_method")
|
||||
codeChallenge := c.Input().Get("code_challenge")
|
||||
|
||||
if challengeMethod != "S256" && challengeMethod != "null" && challengeMethod != "" {
|
||||
c.ResponseError(c.T("auth:Challenge method should be S256"))
|
||||
return
|
||||
}
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, host, c.GetAcceptLanguage())
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetOAuthToken
|
||||
// @Title GetOAuthToken
|
||||
// @Tag Token API
|
||||
|
@ -80,10 +80,11 @@ func (c *ApiController) GetUsers() {
|
||||
// @Title GetUser
|
||||
// @Tag User API
|
||||
// @Description get user
|
||||
// @Param id query string true "The id ( owner/name ) of the user"
|
||||
// @Param id query string false "The id ( owner/name ) of the user"
|
||||
// @Param owner query string false "The owner of the user"
|
||||
// @Param email query string false "The email of the user"
|
||||
// @Param phone query string false "The phone of the user"
|
||||
// @Param userId query string false "The userId of the user"
|
||||
// @Success 200 {object} object.User The Response object
|
||||
// @router /get-user [get]
|
||||
func (c *ApiController) GetUser() {
|
||||
@ -91,16 +92,22 @@ func (c *ApiController) GetUser() {
|
||||
email := c.Input().Get("email")
|
||||
phone := c.Input().Get("phone")
|
||||
userId := c.Input().Get("userId")
|
||||
|
||||
owner := c.Input().Get("owner")
|
||||
if owner == "" {
|
||||
owner, _ = util.GetOwnerAndNameFromId(id)
|
||||
|
||||
var userFromUserId *object.User
|
||||
if userId != "" && owner != "" {
|
||||
userFromUserId = object.GetUserByUserId(owner, userId)
|
||||
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
|
||||
if owner == "" {
|
||||
owner = util.GetOwnerFromId(id)
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(util.GetId("admin", owner))
|
||||
if !organization.IsProfilePublic {
|
||||
requestUserId := c.GetSessionUsername()
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, owner, false, c.GetAcceptLanguage())
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -114,7 +121,7 @@ func (c *ApiController) GetUser() {
|
||||
case phone != "":
|
||||
user = object.GetUserByPhone(owner, phone)
|
||||
case userId != "":
|
||||
user = object.GetUserByUserId(owner, userId)
|
||||
user = userFromUserId
|
||||
default:
|
||||
user = object.GetUser(id)
|
||||
}
|
||||
@ -137,10 +144,6 @@ func (c *ApiController) UpdateUser() {
|
||||
id := c.Input().Get("id")
|
||||
columnsStr := c.Input().Get("columns")
|
||||
|
||||
if id == "" {
|
||||
id = c.GetSessionUsername()
|
||||
}
|
||||
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
@ -148,24 +151,41 @@ func (c *ApiController) UpdateUser() {
|
||||
return
|
||||
}
|
||||
|
||||
if msg := object.CheckUpdateUser(object.GetUser(id), &user, c.GetAcceptLanguage()); msg != "" {
|
||||
if id == "" {
|
||||
id = c.GetSessionUsername()
|
||||
if id == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
}
|
||||
oldUser := object.GetUser(id)
|
||||
if oldUser == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
|
||||
return
|
||||
}
|
||||
|
||||
if oldUser.Owner == "built-in" && oldUser.Name == "admin" && (user.Owner != "built-in" || user.Name != "admin") {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin := c.IsAdmin()
|
||||
if pass, err := object.CheckPermissionForUpdateUser(oldUser, &user, isAdmin, c.GetAcceptLanguage()); !pass {
|
||||
c.ResponseError(err)
|
||||
return
|
||||
}
|
||||
|
||||
columns := []string{}
|
||||
if columnsStr != "" {
|
||||
columns = strings.Split(columnsStr, ",")
|
||||
}
|
||||
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
|
||||
if pass, err := checkPermissionForUpdateUser(id, user, c); !pass {
|
||||
c.ResponseError(err)
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
|
||||
affected := object.UpdateUser(id, &user, columns, isAdmin)
|
||||
if affected {
|
||||
object.UpdateUserToOriginalDatabase(&user)
|
||||
}
|
||||
@ -220,6 +240,11 @@ func (c *ApiController) DeleteUser() {
|
||||
return
|
||||
}
|
||||
|
||||
if user.Owner == "built-in" && user.Name == "admin" {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteUser(&user))
|
||||
c.ServeJSON()
|
||||
}
|
||||
@ -275,14 +300,39 @@ func (c *ApiController) SetPassword() {
|
||||
userName := c.Ctx.Request.Form.Get("userName")
|
||||
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||
code := c.Ctx.Request.Form.Get("code")
|
||||
|
||||
//if userOwner == "built-in" && userName == "admin" {
|
||||
// c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
// return
|
||||
//}
|
||||
|
||||
if strings.Contains(newPassword, " ") {
|
||||
c.ResponseError(c.T("user:New password cannot contain blank space."))
|
||||
return
|
||||
}
|
||||
if len(newPassword) <= 5 {
|
||||
c.ResponseError(c.T("user:New password must have at least 6 characters"))
|
||||
return
|
||||
}
|
||||
|
||||
requestUserId := c.GetSessionUsername()
|
||||
userId := util.GetId(userOwner, userName)
|
||||
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, userOwner, true, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
c.ResponseError(err.Error())
|
||||
requestUserId := c.GetSessionUsername()
|
||||
if requestUserId == "" && code == "" {
|
||||
return
|
||||
} else if code == "" {
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if code != c.GetSession("verifiedCode") {
|
||||
c.ResponseError("")
|
||||
return
|
||||
}
|
||||
c.SetSession("verifiedCode", "")
|
||||
}
|
||||
|
||||
targetUser := object.GetUser(userId)
|
||||
@ -295,16 +345,6 @@ func (c *ApiController) SetPassword() {
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(newPassword, " ") {
|
||||
c.ResponseError(c.T("user:New password cannot contain blank space."))
|
||||
return
|
||||
}
|
||||
|
||||
if len(newPassword) <= 5 {
|
||||
c.ResponseError(c.T("user:New password must have at least 6 characters"))
|
||||
return
|
||||
}
|
||||
|
||||
targetUser.Password = newPassword
|
||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||
c.ResponseOk()
|
||||
|
@ -1,139 +0,0 @@
|
||||
// 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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
func checkPermissionForUpdateUser(userId string, newUser object.User, c *ApiController) (bool, string) {
|
||||
oldUser := object.GetUser(userId)
|
||||
organization := object.GetOrganizationByUser(oldUser)
|
||||
var itemsChanged []*object.AccountItem
|
||||
|
||||
if oldUser.Owner != newUser.Owner {
|
||||
item := object.GetAccountItemByName("Organization", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Name != newUser.Name {
|
||||
item := object.GetAccountItemByName("Name", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Id != newUser.Id {
|
||||
item := object.GetAccountItemByName("ID", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.DisplayName != newUser.DisplayName {
|
||||
item := object.GetAccountItemByName("Display name", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Avatar != newUser.Avatar {
|
||||
item := object.GetAccountItemByName("Avatar", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Type != newUser.Type {
|
||||
item := object.GetAccountItemByName("User type", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
// The password is *** when not modified
|
||||
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
||||
item := object.GetAccountItemByName("Password", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Email != newUser.Email {
|
||||
item := object.GetAccountItemByName("Email", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Phone != newUser.Phone {
|
||||
item := object.GetAccountItemByName("Phone", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.CountryCode != newUser.CountryCode {
|
||||
item := object.GetAccountItemByName("Country code", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Region != newUser.Region {
|
||||
item := object.GetAccountItemByName("Country/Region", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Location != newUser.Location {
|
||||
item := object.GetAccountItemByName("Location", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Affiliation != newUser.Affiliation {
|
||||
item := object.GetAccountItemByName("Affiliation", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Title != newUser.Title {
|
||||
item := object.GetAccountItemByName("Title", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Homepage != newUser.Homepage {
|
||||
item := object.GetAccountItemByName("Homepage", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Bio != newUser.Bio {
|
||||
item := object.GetAccountItemByName("Bio", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Tag != newUser.Tag {
|
||||
item := object.GetAccountItemByName("Tag", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
||||
item := object.GetAccountItemByName("Signup application", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
||||
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
||||
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
||||
item := object.GetAccountItemByName("Properties", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||
item := object.GetAccountItemByName("Is admin", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin {
|
||||
item := object.GetAccountItemByName("Is global admin", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
||||
item := object.GetAccountItemByName("Is forbidden", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
||||
item := object.GetAccountItemByName("Is deleted", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
currentUser := c.getCurrentUser()
|
||||
if currentUser == nil && c.IsGlobalAdmin() {
|
||||
currentUser = &object.User{
|
||||
IsGlobalAdmin: true,
|
||||
}
|
||||
}
|
||||
|
||||
for i := range itemsChanged {
|
||||
if pass, err := object.CheckAccountItemModifyRule(itemsChanged[i], currentUser, c.GetAcceptLanguage()); !pass {
|
||||
return pass, err
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
@ -16,7 +16,6 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
@ -115,12 +114,25 @@ func (c *ApiController) RequireAdmin() (string, bool) {
|
||||
return user.Owner, true
|
||||
}
|
||||
|
||||
func getInitScore(organization *object.Organization) (int, error) {
|
||||
if organization != nil {
|
||||
return organization.InitScore, nil
|
||||
} else {
|
||||
return strconv.Atoi(conf.GetConfigString("initScore"))
|
||||
// IsMaskedEnabled ...
|
||||
func (c *ApiController) IsMaskedEnabled() (bool, bool) {
|
||||
isMaskEnabled := true
|
||||
withSecret := c.Input().Get("withSecret")
|
||||
if withSecret == "1" {
|
||||
isMaskEnabled = false
|
||||
|
||||
if conf.IsDemoMode() {
|
||||
c.ResponseError(c.T("general:this operation is not allowed in demo mode"))
|
||||
return false, isMaskEnabled
|
||||
}
|
||||
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return false, isMaskEnabled
|
||||
}
|
||||
}
|
||||
|
||||
return true, isMaskEnabled
|
||||
}
|
||||
|
||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
|
||||
@ -128,7 +140,7 @@ func (c *ApiController) GetProviderFromContext(category string) (*object.Provide
|
||||
if providerName != "" {
|
||||
provider := object.GetProvider(util.GetId("admin", providerName))
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("util:The provider: %s is not found"), providerName)
|
||||
c.ResponseError(fmt.Sprintf(c.T("util:The provider: %s is not found"), providerName))
|
||||
return nil, nil, false
|
||||
}
|
||||
return provider, nil, true
|
||||
|
@ -15,76 +15,49 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/captcha"
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
SignupVerification = "signup"
|
||||
ResetVerification = "reset"
|
||||
LoginVerification = "login"
|
||||
ForgetVerification = "forget"
|
||||
SignupVerification = "signup"
|
||||
ResetVerification = "reset"
|
||||
LoginVerification = "login"
|
||||
ForgetVerification = "forget"
|
||||
MfaSetupVerification = "mfaSetup"
|
||||
MfaAuthVerification = "mfaAuth"
|
||||
)
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
user = nil
|
||||
} else {
|
||||
user = object.GetUser(userId)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// SendVerificationCode ...
|
||||
// @Title SendVerificationCode
|
||||
// @Tag Verification API
|
||||
// @router /send-verification-code [post]
|
||||
func (c *ApiController) SendVerificationCode() {
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
countryCode := c.Ctx.Request.Form.Get("countryCode")
|
||||
checkType := c.Ctx.Request.Form.Get("checkType")
|
||||
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
|
||||
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
|
||||
applicationId := c.Ctx.Request.Form.Get("applicationId")
|
||||
method := c.Ctx.Request.Form.Get("method")
|
||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
||||
|
||||
if dest == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": dest.")
|
||||
return
|
||||
}
|
||||
if applicationId == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": applicationId.")
|
||||
return
|
||||
}
|
||||
if checkType == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": checkType.")
|
||||
return
|
||||
}
|
||||
if !strings.Contains(applicationId, "/") {
|
||||
c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.")
|
||||
if msg := vform.CheckParameter(form.SendVerifyCode, c.GetAcceptLanguage()); msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if checkType != "none" {
|
||||
if captchaToken == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.")
|
||||
if vform.CaptchaType != "none" {
|
||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider := captcha.GetCaptchaProvider(checkType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + checkType)
|
||||
return
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(captchaToken, clientSecret); err != nil {
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if !isHuman {
|
||||
@ -93,7 +66,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
}
|
||||
}
|
||||
|
||||
application := object.GetApplication(applicationId)
|
||||
application := object.GetApplication(vform.ApplicationId)
|
||||
organization := object.GetOrganization(util.GetId(application.Owner, application.Organization))
|
||||
if organization == nil {
|
||||
c.ResponseError(c.T("check:Organization does not exist"))
|
||||
@ -102,63 +75,85 @@ func (c *ApiController) SendVerificationCode() {
|
||||
|
||||
var user *object.User
|
||||
// checkUser != "", means method is ForgetVerification
|
||||
if checkUser != "" {
|
||||
if vform.CheckUser != "" {
|
||||
owner := application.Organization
|
||||
user = object.GetUser(util.GetId(owner, checkUser))
|
||||
user = object.GetUser(util.GetId(owner, vform.CheckUser))
|
||||
}
|
||||
|
||||
// mfaSessionData != nil, means method is MfaSetupVerification
|
||||
if mfaSessionData := c.getMfaSessionData(); mfaSessionData != nil {
|
||||
user = object.GetUser(mfaSessionData.UserId)
|
||||
}
|
||||
|
||||
sendResp := errors.New("invalid dest type")
|
||||
|
||||
switch destType {
|
||||
case "email":
|
||||
if !util.IsEmailValid(dest) {
|
||||
switch vform.Type {
|
||||
case object.VerifyTypeEmail:
|
||||
if !util.IsEmailValid(vform.Dest) {
|
||||
c.ResponseError(c.T("check:Email is invalid"))
|
||||
return
|
||||
}
|
||||
|
||||
if method == LoginVerification || method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == dest {
|
||||
dest = user.Email
|
||||
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == vform.Dest {
|
||||
vform.Dest = user.Email
|
||||
}
|
||||
|
||||
user = object.GetUserByEmail(organization.Name, dest)
|
||||
user = object.GetUserByEmail(organization.Name, vform.Dest)
|
||||
if user == nil {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
} else if method == ResetVerification {
|
||||
} else if vform.Method == ResetVerification {
|
||||
user = c.getCurrentUser()
|
||||
} else if vform.Method == MfaAuthVerification {
|
||||
mfaProps := user.GetPreferMfa(false)
|
||||
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
|
||||
vform.Dest = mfaProps.Secret
|
||||
}
|
||||
}
|
||||
|
||||
provider := application.GetEmailProvider()
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
case "phone":
|
||||
if method == LoginVerification || method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||
dest = user.Phone
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest)
|
||||
case object.VerifyTypePhone:
|
||||
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {
|
||||
vform.Dest = user.Phone
|
||||
}
|
||||
|
||||
if user = object.GetUserByPhone(organization.Name, dest); user == nil {
|
||||
if user = object.GetUserByPhone(organization.Name, vform.Dest); user == nil {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
|
||||
countryCode = user.GetCountryCode(countryCode)
|
||||
} else if method == ResetVerification {
|
||||
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||
} else if vform.Method == ResetVerification {
|
||||
if user = c.getCurrentUser(); user != nil {
|
||||
countryCode = user.GetCountryCode(countryCode)
|
||||
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||
}
|
||||
} else if vform.Method == MfaAuthVerification {
|
||||
mfaProps := user.GetPreferMfa(false)
|
||||
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
|
||||
vform.Dest = mfaProps.Secret
|
||||
}
|
||||
|
||||
vform.CountryCode = mfaProps.CountryCode
|
||||
}
|
||||
|
||||
provider := application.GetSmsProvider()
|
||||
if phone, ok := util.GetE164Number(dest, countryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), countryCode))
|
||||
if phone, ok := util.GetE164Number(vform.Dest, vform.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
|
||||
return
|
||||
} else {
|
||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, phone)
|
||||
}
|
||||
}
|
||||
|
||||
if vform.Method == MfaSetupVerification {
|
||||
c.SetSession(object.MfaSmsCountryCodeSession, vform.CountryCode)
|
||||
c.SetSession(object.MfaSmsDestSession, vform.Dest)
|
||||
}
|
||||
|
||||
if sendResp != nil {
|
||||
c.ResponseError(sendResp.Error())
|
||||
} else {
|
||||
@ -166,6 +161,38 @@ func (c *ApiController) SendVerificationCode() {
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyCaptcha ...
|
||||
// @Title VerifyCaptcha
|
||||
// @Tag Verification API
|
||||
// @router /verify-captcha [post]
|
||||
func (c *ApiController) VerifyCaptcha() {
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if msg := vform.CheckParameter(form.VerifyCaptcha, c.GetAcceptLanguage()); msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||
return
|
||||
}
|
||||
|
||||
isValid, err := provider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(isValid)
|
||||
}
|
||||
|
||||
// ResetEmailOrPhone ...
|
||||
// @Tag Account API
|
||||
// @Title ResetEmailOrPhone
|
||||
@ -187,7 +214,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
|
||||
checkDest := dest
|
||||
organization := object.GetOrganizationByUser(user)
|
||||
if destType == "phone" {
|
||||
if destType == object.VerifyTypePhone {
|
||||
if object.HasUserByField(user.Owner, "phone", dest) {
|
||||
c.ResponseError(c.T("check:Phone already exists"))
|
||||
return
|
||||
@ -199,7 +226,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
return
|
||||
}
|
||||
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user, c.GetAcceptLanguage()); !pass {
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
|
||||
c.ResponseError(errMsg)
|
||||
return
|
||||
}
|
||||
@ -207,7 +234,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
|
||||
return
|
||||
}
|
||||
} else if destType == "email" {
|
||||
} else if destType == object.VerifyTypeEmail {
|
||||
if object.HasUserByField(user.Owner, "email", dest) {
|
||||
c.ResponseError(c.T("check:Email already exists"))
|
||||
return
|
||||
@ -219,21 +246,22 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
return
|
||||
}
|
||||
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user, c.GetAcceptLanguage()); !pass {
|
||||
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
|
||||
c.ResponseError(errMsg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess {
|
||||
c.ResponseError(result.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
switch destType {
|
||||
case "email":
|
||||
case object.VerifyTypeEmail:
|
||||
user.Email = dest
|
||||
object.SetUserField(user, "email", user.Email)
|
||||
case "phone":
|
||||
case object.VerifyTypePhone:
|
||||
user.Phone = dest
|
||||
object.SetUserField(user, "phone", user.Phone)
|
||||
default:
|
||||
@ -245,35 +273,56 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// VerifyCaptcha ...
|
||||
// @Title VerifyCaptcha
|
||||
// VerifyCode
|
||||
// @Tag Verification API
|
||||
// @router /verify-captcha [post]
|
||||
func (c *ApiController) VerifyCaptcha() {
|
||||
captchaType := c.Ctx.Request.Form.Get("captchaType")
|
||||
|
||||
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
|
||||
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
|
||||
if captchaToken == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.")
|
||||
return
|
||||
}
|
||||
if clientSecret == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter") + ": clientSecret.")
|
||||
return
|
||||
}
|
||||
|
||||
provider := captcha.GetCaptchaProvider(captchaType)
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||
return
|
||||
}
|
||||
|
||||
isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret)
|
||||
// @Title VerifyCode
|
||||
// @router /api/verify-code [post]
|
||||
func (c *ApiController) VerifyCode() {
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(isValid)
|
||||
var user *object.User
|
||||
if authForm.Name != "" {
|
||||
user = object.GetUserByFields(authForm.Organization, authForm.Name)
|
||||
}
|
||||
|
||||
var checkDest string
|
||||
if strings.Contains(authForm.Username, "@") {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == authForm.Username {
|
||||
authForm.Username = user.Email
|
||||
}
|
||||
checkDest = authForm.Username
|
||||
} else {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == authForm.Username {
|
||||
authForm.Username = user.Phone
|
||||
}
|
||||
}
|
||||
|
||||
if user = object.GetUserByFields(authForm.Organization, authForm.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username)))
|
||||
return
|
||||
}
|
||||
|
||||
verificationCodeType := object.GetVerifyType(authForm.Username)
|
||||
if verificationCodeType == object.VerifyTypePhone {
|
||||
authForm.CountryCode = user.GetCountryCode(authForm.CountryCode)
|
||||
var ok bool
|
||||
if checkDest, ok = util.GetE164Number(authForm.Username, authForm.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result := object.CheckVerificationCode(checkDest, authForm.Code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess {
|
||||
c.ResponseError(result.Msg)
|
||||
return
|
||||
}
|
||||
object.DisableVerificationCode(checkDest)
|
||||
c.SetSession("verifiedCode", authForm.Code)
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
@ -147,9 +148,9 @@ func (c *ApiController) WebAuthnSigninFinish() {
|
||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||
|
||||
application := object.GetApplicationByUser(user)
|
||||
var form RequestForm
|
||||
form.Type = responseType
|
||||
resp := c.HandleLoggedIn(application, user, &form)
|
||||
var authForm form.AuthForm
|
||||
authForm.Type = responseType
|
||||
resp := c.HandleLoggedIn(application, user, &authForm)
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@ -37,13 +37,14 @@ func (c *ApiController) GetWebhooks() {
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
organization := c.Input().Get("organization")
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetWebhooks(owner)
|
||||
c.Data["json"] = object.GetWebhooks(owner, organization)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, field, value)))
|
||||
webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, organization, field, value)))
|
||||
webhooks := object.GetPaginationWebhooks(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(webhooks, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
57
form/auth.go
Normal file
57
form/auth.go
Normal file
@ -0,0 +1,57 @@
|
||||
// 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 form
|
||||
|
||||
type AuthForm struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
Organization string `json:"organization"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
IdCard string `json:"idCard"`
|
||||
Region string `json:"region"`
|
||||
|
||||
Application string `json:"application"`
|
||||
ClientId string `json:"clientId"`
|
||||
Provider string `json:"provider"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
Method string `json:"method"`
|
||||
|
||||
EmailCode string `json:"emailCode"`
|
||||
PhoneCode string `json:"phoneCode"`
|
||||
CountryCode string `json:"countryCode"`
|
||||
|
||||
AutoSignin bool `json:"autoSignin"`
|
||||
|
||||
RelayState string `json:"relayState"`
|
||||
SamlRequest string `json:"samlRequest"`
|
||||
SamlResponse string `json:"samlResponse"`
|
||||
|
||||
CaptchaType string `json:"captchaType"`
|
||||
CaptchaToken string `json:"captchaToken"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
|
||||
MfaType string `json:"mfaType"`
|
||||
Passcode string `json:"passcode"`
|
||||
RecoveryCode string `json:"recoveryCode"`
|
||||
}
|
67
form/verification.go
Normal file
67
form/verification.go
Normal file
@ -0,0 +1,67 @@
|
||||
// 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 form
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
)
|
||||
|
||||
type VerificationForm struct {
|
||||
Dest string `form:"dest"`
|
||||
Type string `form:"type"`
|
||||
CountryCode string `form:"countryCode"`
|
||||
ApplicationId string `form:"applicationId"`
|
||||
Method string `form:"method"`
|
||||
CheckUser string `form:"checkUser"`
|
||||
|
||||
CaptchaType string `form:"captchaType"`
|
||||
ClientSecret string `form:"clientSecret"`
|
||||
CaptchaToken string `form:"captchaToken"`
|
||||
}
|
||||
|
||||
const (
|
||||
SendVerifyCode = 0
|
||||
VerifyCaptcha = 1
|
||||
)
|
||||
|
||||
func (form *VerificationForm) CheckParameter(checkType int, lang string) string {
|
||||
if checkType == SendVerifyCode {
|
||||
if form.Type == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": type."
|
||||
}
|
||||
if form.Dest == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": dest."
|
||||
}
|
||||
if form.CaptchaType == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": checkType."
|
||||
}
|
||||
if !strings.Contains(form.ApplicationId, "/") {
|
||||
return i18n.Translate(lang, "verification:Wrong parameter") + ": applicationId."
|
||||
}
|
||||
}
|
||||
|
||||
if form.CaptchaType != "none" {
|
||||
if form.CaptchaToken == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": captchaToken."
|
||||
}
|
||||
if form.ClientSecret == "" {
|
||||
return i18n.Translate(lang, "general:Missing parameter") + ": clientSecret."
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
11
go.mod
11
go.mod
@ -17,14 +17,17 @@ require (
|
||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/denisenkom/go-mssqldb v0.9.0
|
||||
github.com/dlclark/regexp2 v1.9.0 // indirect
|
||||
github.com/fogleman/gg v1.3.0
|
||||
github.com/forestmgy/ldapserver v1.1.0
|
||||
github.com/go-git/go-git/v5 v5.6.0
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
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-webauthn/webauthn v0.8.2
|
||||
github.com/go-webauthn/webauthn v0.6.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
@ -34,15 +37,19 @@ require (
|
||||
github.com/markbates/goth v1.75.2
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/nyaruka/phonenumbers v1.1.5
|
||||
github.com/pkoukk/tiktoken-go v0.1.1
|
||||
github.com/prometheus/client_golang v1.7.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/gosaml2 v0.6.0
|
||||
github.com/russellhaering/goxmldsig v1.1.1
|
||||
github.com/sashabaranov/go-openai v1.9.1
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
|
29
go.sum
29
go.sum
@ -161,6 +161,9 @@ github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D
|
||||
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
|
||||
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
@ -173,6 +176,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/forestmgy/ldapserver v1.1.0 h1:gvil4nuLhqPEL8SugCkFhRyA0/lIvRdwZSqlrw63ll4=
|
||||
github.com/forestmgy/ldapserver v1.1.0/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||
@ -222,10 +227,10 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8RknbS0=
|
||||
github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w=
|
||||
github.com/go-webauthn/webauthn v0.8.2 h1:8KLIbpldjz9KVGHfqEgJNbkhd7bbRXhNw4QWFJE15oA=
|
||||
github.com/go-webauthn/webauthn v0.8.2/go.mod h1:d+ezx/jMCNDiqSMzOchuynKb9CVU1NM9BumOnokfcVQ=
|
||||
github.com/go-webauthn/revoke v0.1.6 h1:3tv+itza9WpX5tryRQx4GwxCCBrCIiJ8GIkOhxiAmmU=
|
||||
github.com/go-webauthn/revoke v0.1.6/go.mod h1:TB4wuW4tPlwgF3znujA96F70/YSQXHPPWl7vgY09Iy8=
|
||||
github.com/go-webauthn/webauthn v0.6.0 h1:uLInMApSvBfP+vEFasNE0rnVPG++fjp7lmAIvNhe+UU=
|
||||
github.com/go-webauthn/webauthn v0.6.0/go.mod h1:7edMRZXwuM6JIVjN68G24Bzt+bPCvTmjiL0j+cAmXtY=
|
||||
github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
|
||||
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@ -234,10 +239,13 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -471,6 +479,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo=
|
||||
github.com/pkoukk/tiktoken-go v0.1.1/go.mod h1:boMWvk9pQCOTx11pgu0DrIdrAKgQzzJKUP6vLXaz7Rw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
@ -515,6 +525,8 @@ github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5se
|
||||
github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sashabaranov/go-openai v1.9.1 h1:3N52HkJKo9Zlo/oe1AVv5ZkCOny0ra58/ACvAxkN3MM=
|
||||
github.com/sashabaranov/go-openai v1.9.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
@ -566,8 +578,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
@ -658,8 +671,10 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -674,6 +689,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@ -741,6 +757,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -833,6 +850,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -843,6 +861,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -32,6 +32,7 @@ func TestGenerateI18nFrontend(t *testing.T) {
|
||||
applyToOtherLanguage("frontend", "ko", data)
|
||||
applyToOtherLanguage("frontend", "ru", data)
|
||||
applyToOtherLanguage("frontend", "vi", data)
|
||||
applyToOtherLanguage("frontend", "pt", data)
|
||||
}
|
||||
|
||||
func TestGenerateI18nBackend(t *testing.T) {
|
||||
@ -47,4 +48,5 @@ func TestGenerateI18nBackend(t *testing.T) {
|
||||
applyToOtherLanguage("backend", "ko", data)
|
||||
applyToOtherLanguage("backend", "ru", data)
|
||||
applyToOtherLanguage("backend", "vi", data)
|
||||
applyToOtherLanguage("backend", "pt", data)
|
||||
}
|
||||
|
@ -23,6 +23,14 @@
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s und %s stimmen nicht überein"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Zugehörigkeit darf nicht leer sein",
|
||||
"DisplayName cannot be blank": "Anzeigename kann nicht leer sein",
|
||||
@ -32,8 +40,8 @@
|
||||
"Email is invalid": "E-Mail ist ungültig",
|
||||
"Empty username.": "Leerer Benutzername.",
|
||||
"FirstName cannot be blank": "Vorname darf nicht leer sein",
|
||||
"LDAP user name or password incorrect": "Ldap Benutzername oder Passwort falsch",
|
||||
"LastName cannot be blank": "Nachname darf nicht leer sein",
|
||||
"Ldap user name or password incorrect": "Ldap Benutzername oder Passwort falsch",
|
||||
"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",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "Die Telefonnummer ist ungültig",
|
||||
"Session outdated, please login again": "Sitzung abgelaufen, bitte erneut anmelden",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Dem Benutzer ist der Zugang verboten, bitte kontaktieren Sie den Administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Der Benutzername darf nur alphanumerische Zeichen, Unterstriche oder Bindestriche enthalten, keine aufeinanderfolgenden Bindestriche oder Unterstriche haben und darf nicht mit einem Bindestrich oder Unterstrich beginnen oder enden.",
|
||||
"Username already exists": "Benutzername existiert bereits",
|
||||
"Username cannot be an email address": "Benutzername kann keine E-Mail-Adresse sein",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "Benutzername muss mindestens 2 Zeichen lang sein",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Sie haben zu oft das falsche Passwort oder den falschen Code eingegeben. Bitte warten Sie %d Minuten und versuchen Sie es erneut",
|
||||
"Your region is not allow to signup by phone": "Ihre Region ist nicht berechtigt, sich telefonisch anzumelden",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Das Passwort oder der Code ist falsch. Du hast noch %d Versuche übrig",
|
||||
"unsupported password type: %s": "Nicht unterstützter Passworttyp: %s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "Nicht in der Lage, die E-Mail-Änderungsregel zu erhalten.",
|
||||
"Unable to get the phone modify rule.": "Nicht in der Lage, die Telefon-Änderungsregel zu erhalten.",
|
||||
"Unknown type": "Unbekannter Typ",
|
||||
"Wrong parameter": "Falscher Parameter",
|
||||
"Wrong verification code!": "Falscher Bestätigungscode!",
|
||||
"You should verify your code in %d min!": "Du solltest deinen Code in %d Minuten verifizieren!",
|
||||
"the user does not exist, please sign up first": "Der Benutzer existiert nicht, bitte zuerst anmelden"
|
||||
|
@ -23,6 +23,14 @@
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
@ -32,8 +40,8 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Ldap user name or password incorrect": "Ldap user name or password incorrect",
|
||||
"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",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "Username must have at least 2 characters",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
|
||||
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
|
||||
"unsupported password type: %s": "unsupported password type: %s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
|
||||
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
|
||||
"Unknown type": "Unknown type",
|
||||
"Wrong parameter": "Wrong parameter",
|
||||
"Wrong verification code!": "Wrong verification code!",
|
||||
"You should verify your code in %d min!": "You should verify your code in %d min!",
|
||||
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
|
||||
|
@ -23,6 +23,14 @@
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Los servicios %s y %s no coinciden"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Afiliación no puede estar en blanco",
|
||||
"DisplayName cannot be blank": "El nombre de visualización no puede estar en blanco",
|
||||
@ -32,8 +40,8 @@
|
||||
"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",
|
||||
"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",
|
||||
"Ldap user name or password incorrect": "Nombre de usuario o contraseña de Ldap incorrectos",
|
||||
"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",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "El número de teléfono no es válido",
|
||||
"Session outdated, please login again": "Sesión expirada, por favor vuelva a iniciar sesión",
|
||||
"The user is forbidden to sign in, please contact the administrator": "El usuario no está autorizado a iniciar sesión, por favor contacte al administrador",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "El nombre de usuario solo puede contener caracteres alfanuméricos, guiones bajos o guiones, no puede tener guiones o subrayados consecutivos, y no puede comenzar ni terminar con un guión o subrayado.",
|
||||
"Username already exists": "El nombre de usuario ya existe",
|
||||
"Username cannot be an email address": "Nombre de usuario no puede ser una dirección de correo electrónico",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "Nombre de usuario debe tener al menos 2 caracteres",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Has ingresado la contraseña o código incorrecto demasiadas veces, por favor espera %d minutos e intenta de nuevo",
|
||||
"Your region is not allow to signup by phone": "Tu región no está permitida para registrarse por teléfono",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Contraseña o código incorrecto, tienes %d intentos restantes",
|
||||
"unsupported password type: %s": "Tipo de contraseña no compatible: %s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "No se puede obtener la regla de modificación de correo electrónico.",
|
||||
"Unable to get the phone modify rule.": "No se pudo obtener la regla de modificación del teléfono.",
|
||||
"Unknown type": "Tipo desconocido",
|
||||
"Wrong parameter": "Parámetro incorrecto",
|
||||
"Wrong verification code!": "¡Código de verificación incorrecto!",
|
||||
"You should verify your code in %d min!": "¡Deberías verificar tu código en %d minutos!",
|
||||
"the user does not exist, please sign up first": "El usuario no existe, por favor regístrese primero"
|
||||
|
@ -23,6 +23,14 @@
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Affiliation ne peut pas être vide",
|
||||
"DisplayName cannot be blank": "Le nom d'affichage ne peut pas être vide",
|
||||
@ -32,8 +40,8 @@
|
||||
"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",
|
||||
"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",
|
||||
"Ldap user name or password incorrect": "Nom d'utilisateur ou mot de passe LDAP incorrect",
|
||||
"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",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "Le numéro de téléphone est invalide",
|
||||
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
|
||||
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
|
||||
"Username already exists": "Nom d'utilisateur existe déjà",
|
||||
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer",
|
||||
"Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances",
|
||||
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "Incapable d'obtenir la règle de modification de courriel.",
|
||||
"Unable to get the phone modify rule.": "Impossible d'obtenir la règle de modification de téléphone.",
|
||||
"Unknown type": "Type inconnu",
|
||||
"Wrong parameter": "Mauvais paramètre",
|
||||
"Wrong verification code!": "Mauvais code de vérification !",
|
||||
"You should verify your code in %d min!": "Vous devriez vérifier votre code en %d min !",
|
||||
"the user does not exist, please sign up first": "L'utilisateur n'existe pas, veuillez vous inscrire d'abord"
|
||||
|
@ -23,6 +23,14 @@
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Layanan %s dan %s tidak cocok"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Keterkaitan tidak boleh kosong",
|
||||
"DisplayName cannot be blank": "Nama Pengguna tidak boleh kosong",
|
||||
@ -32,8 +40,8 @@
|
||||
"Email is invalid": "Email tidak valid",
|
||||
"Empty username.": "Nama pengguna kosong.",
|
||||
"FirstName cannot be blank": "Nama depan tidak boleh kosong",
|
||||
"LDAP user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
|
||||
"LastName cannot be blank": "Nama belakang tidak boleh kosong",
|
||||
"Ldap user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
|
||||
"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",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "Nomor telepon tidak valid",
|
||||
"Session outdated, please login again": "Sesi kedaluwarsa, silakan masuk lagi",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Pengguna dilarang masuk, silakan hubungi administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Nama pengguna hanya bisa menggunakan karakter alfanumerik, garis bawah atau tanda hubung, tidak boleh memiliki dua tanda hubung atau garis bawah berurutan, dan tidak boleh diawali atau diakhiri dengan tanda hubung atau garis bawah.",
|
||||
"Username already exists": "Nama pengguna sudah ada",
|
||||
"Username cannot be an email address": "Username tidak bisa menjadi alamat email",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "Nama pengguna harus memiliki setidaknya 2 karakter",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Anda telah memasukkan kata sandi atau kode yang salah terlalu banyak kali, mohon tunggu selama %d menit dan coba lagi",
|
||||
"Your region is not allow to signup by phone": "Wilayah Anda tidak diizinkan untuk mendaftar melalui telepon",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Kata sandi atau kode salah, Anda memiliki %d kesempatan tersisa",
|
||||
"unsupported password type: %s": "jenis sandi tidak didukung: %s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "Tidak dapat memperoleh aturan modifikasi email.",
|
||||
"Unable to get the phone modify rule.": "Tidak dapat memodifikasi aturan telepon.",
|
||||
"Unknown type": "Tipe tidak diketahui",
|
||||
"Wrong parameter": "Parameter yang salah",
|
||||
"Wrong verification code!": "Kode verifikasi salah!",
|
||||
"You should verify your code in %d min!": "Anda harus memverifikasi kode Anda dalam %d menit!",
|
||||
"the user does not exist, please sign up first": "Pengguna tidak ada, silakan daftar terlebih dahulu"
|
||||
|
@ -23,6 +23,14 @@
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "サービス%sと%sは一致しません"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "所属は空白にできません",
|
||||
"DisplayName cannot be blank": "表示名は空白にできません",
|
||||
@ -32,8 +40,8 @@
|
||||
"Email is invalid": "電子メールは無効です",
|
||||
"Empty username.": "空のユーザー名。",
|
||||
"FirstName cannot be blank": "ファーストネームは空白にできません",
|
||||
"LDAP user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
|
||||
"LastName cannot be blank": "姓は空白にできません",
|
||||
"Ldap user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
|
||||
"Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください",
|
||||
"Organization does not exist": "組織は存在しません",
|
||||
"Password must have at least 6 characters": "パスワードは少なくとも6つの文字が必要です",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "電話番号が無効です",
|
||||
"Session outdated, please login again": "セッションが期限切れになりました。再度ログインしてください",
|
||||
"The user is forbidden to sign in, please contact the administrator": "ユーザーはサインインできません。管理者に連絡してください",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "ユーザー名には英数字、アンダースコア、ハイフンしか含めることができません。連続したハイフンまたはアンダースコアは不可であり、ハイフンまたはアンダースコアで始まるまたは終わることもできません。",
|
||||
"Username already exists": "ユーザー名はすでに存在しています",
|
||||
"Username cannot be an email address": "ユーザー名には電子メールアドレスを使用できません",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "ユーザー名は少なくとも2文字必要です",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "あなたは間違ったパスワードまたはコードを何度も入力しました。%d 分間待ってから再度お試しください",
|
||||
"Your region is not allow to signup by phone": "あなたの地域は電話でサインアップすることができません",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "パスワードまたはコードが間違っています。あと%d回の試行機会があります",
|
||||
"unsupported password type: %s": "サポートされていないパスワードタイプ:%s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "電子メール変更規則を取得できません。",
|
||||
"Unable to get the phone modify rule.": "電話の変更ルールを取得できません。",
|
||||
"Unknown type": "不明なタイプ",
|
||||
"Wrong parameter": "誤ったパラメータ",
|
||||
"Wrong verification code!": "誤った検証コードです!",
|
||||
"You should verify your code in %d min!": "あなたは%d分であなたのコードを確認する必要があります!",
|
||||
"the user does not exist, please sign up first": "ユーザーは存在しません。まず登録してください"
|
||||
|
@ -23,6 +23,14 @@
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "소속은 비워 둘 수 없습니다",
|
||||
"DisplayName cannot be blank": "DisplayName는 비어 있을 수 없습니다",
|
||||
@ -32,8 +40,8 @@
|
||||
"Email is invalid": "이메일이 유효하지 않습니다",
|
||||
"Empty username.": "빈 사용자 이름.",
|
||||
"FirstName cannot be blank": "이름은 공백일 수 없습니다",
|
||||
"LDAP user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
|
||||
"LastName cannot be blank": "성은 비어 있을 수 없습니다",
|
||||
"Ldap user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
|
||||
"Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요",
|
||||
"Organization does not exist": "조직은 존재하지 않습니다",
|
||||
"Password must have at least 6 characters": "암호는 적어도 6자 이상이어야 합니다",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "전화번호가 유효하지 않습니다",
|
||||
"Session outdated, please login again": "세션이 만료되었습니다. 다시 로그인해주세요",
|
||||
"The user is forbidden to sign in, please contact the administrator": "사용자는 로그인이 금지되어 있습니다. 관리자에게 문의하십시오",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "사용자 이름은 알파벳, 숫자, 밑줄 또는 하이픈만 포함할 수 있으며, 연속된 하이픈 또는 밑줄을 가질 수 없으며, 하이픈 또는 밑줄로 시작하거나 끝날 수 없습니다.",
|
||||
"Username already exists": "사용자 이름이 이미 존재합니다",
|
||||
"Username cannot be an email address": "사용자 이름은 이메일 주소가 될 수 없습니다",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "사용자 이름은 적어도 2개의 문자가 있어야 합니다",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "올바르지 않은 비밀번호나 코드를 여러 번 입력했습니다. %d분 동안 기다리신 후 다시 시도해주세요",
|
||||
"Your region is not allow to signup by phone": "당신의 지역은 전화로 가입할 수 없습니다",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "암호 또는 코드가 올바르지 않습니다. %d번의 기회가 남아 있습니다",
|
||||
"unsupported password type: %s": "지원되지 않는 암호 유형: %s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "이메일 수정 규칙을 가져올 수 없습니다.",
|
||||
"Unable to get the phone modify rule.": "전화 수정 규칙을 가져올 수 없습니다.",
|
||||
"Unknown type": "알 수 없는 유형",
|
||||
"Wrong parameter": "잘못된 매개 변수입니다",
|
||||
"Wrong verification code!": "잘못된 인증 코드입니다!",
|
||||
"You should verify your code in %d min!": "당신은 %d분 안에 코드를 검증해야 합니다!",
|
||||
"the user does not exist, please sign up first": "사용자가 존재하지 않습니다. 먼저 회원 가입 해주세요"
|
||||
|
@ -23,6 +23,14 @@
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Сервисы %s и %s не совпадают"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Принадлежность не может быть пустым значением",
|
||||
"DisplayName cannot be blank": "Имя отображения не может быть пустым",
|
||||
@ -32,8 +40,8 @@
|
||||
"Email is invalid": "Адрес электронной почты недействительный",
|
||||
"Empty username.": "Пустое имя пользователя.",
|
||||
"FirstName cannot be blank": "Имя не может быть пустым",
|
||||
"LDAP user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
|
||||
"LastName cannot be blank": "Фамилия не может быть пустой",
|
||||
"Ldap user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
|
||||
"Organization does not exist": "Организация не существует",
|
||||
"Password must have at least 6 characters": "Пароль должен содержать не менее 6 символов",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "Номер телефона является недействительным",
|
||||
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.",
|
||||
"Username already exists": "Имя пользователя уже существует",
|
||||
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "Имя пользователя должно содержать не менее 2 символов",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Вы ввели неправильный пароль или код слишком много раз, пожалуйста, подождите %d минут и попробуйте снова",
|
||||
"Your region is not allow to signup by phone": "Ваш регион не разрешает регистрацию по телефону",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Неправильный пароль или код, у вас осталось %d попыток",
|
||||
"unsupported password type: %s": "неподдерживаемый тип пароля: %s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "Невозможно получить правило изменения электронной почты.",
|
||||
"Unable to get the phone modify rule.": "Невозможно получить правило изменения телефона.",
|
||||
"Unknown type": "Неизвестный тип",
|
||||
"Wrong parameter": "Неправильный параметр",
|
||||
"Wrong verification code!": "Неправильный код подтверждения!",
|
||||
"You should verify your code in %d min!": "Вы должны проверить свой код через %d минут!",
|
||||
"the user does not exist, please sign up first": "Пользователь не существует, пожалуйста, сначала зарегистрируйтесь"
|
||||
|
@ -23,6 +23,14 @@
|
||||
"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"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "Tình trạng liên kết không thể để trống",
|
||||
"DisplayName cannot be blank": "Tên hiển thị không thể để trống",
|
||||
@ -32,8 +40,8 @@
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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ự",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "Số điện thoại không hợp lệ",
|
||||
"Session outdated, please login again": "Phiên làm việc hết hạn, vui lòng đăng nhập lại",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Người dùng bị cấm đăng nhập, vui lòng liên hệ với quản trị viên",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Tên người dùng chỉ có thể chứa các ký tự chữ và số, gạch dưới hoặc gạch ngang, không được có hai ký tự gạch dưới hoặc gạch ngang liền kề và không được bắt đầu hoặc kết thúc bằng dấu gạch dưới hoặc gạch ngang.",
|
||||
"Username already exists": "Tên đăng nhập đã tồn tại",
|
||||
"Username cannot be an email address": "Tên người dùng không thể là địa chỉ email",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "Tên đăng nhập phải có ít nhất 2 ký tự",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Bạn đã nhập sai mật khẩu hoặc mã quá nhiều lần, vui lòng đợi %d phút và thử lại",
|
||||
"Your region is not allow to signup by phone": "Vùng của bạn không được phép đăng ký bằng điện thoại",
|
||||
"password or code is incorrect": "password or code is incorrect",
|
||||
"password or code is incorrect, you have %d remaining chances": "Mật khẩu hoặc mã không chính xác, bạn còn %d lần cơ hội",
|
||||
"unsupported password type: %s": "Loại mật khẩu không được hỗ trợ: %s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "Không thể lấy quy tắc sửa đổi email.",
|
||||
"Unable to get the phone modify rule.": "Không thể thay đổi quy tắc trên điện thoại.",
|
||||
"Unknown type": "Loại không xác định",
|
||||
"Wrong parameter": "Tham số không đúng",
|
||||
"Wrong verification code!": "Mã xác thực sai!",
|
||||
"You should verify your code in %d min!": "Bạn nên kiểm tra mã của mình trong %d phút!",
|
||||
"the user does not exist, please sign up first": "Người dùng không tồn tại, vui lòng đăng ký trước"
|
||||
|
@ -23,6 +23,14 @@
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "服务%s与%s不匹配"
|
||||
},
|
||||
"chat": {
|
||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
||||
"The chat: %s is not found": "The chat: %s is not found",
|
||||
"The message is invalid": "The message is invalid",
|
||||
"The message: %s is not found": "The message: %s is not found",
|
||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
||||
"The provider: %s is not found": "The provider: %s is not found"
|
||||
},
|
||||
"check": {
|
||||
"Affiliation cannot be blank": "工作单位不可为空",
|
||||
"DisplayName cannot be blank": "显示名称不可为空",
|
||||
@ -32,8 +40,8 @@
|
||||
"Email is invalid": "无效邮箱",
|
||||
"Empty username.": "用户名不可为空",
|
||||
"FirstName cannot be blank": "名不可以为空",
|
||||
"LDAP user name or password incorrect": "LDAP密码错误",
|
||||
"LastName cannot be blank": "姓不可以为空",
|
||||
"Ldap user name or password incorrect": "LDAP密码错误",
|
||||
"Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid,请检查您的 LDAP 服务器",
|
||||
"Organization does not exist": "组织不存在",
|
||||
"Password must have at least 6 characters": "新密码至少为6位",
|
||||
@ -42,6 +50,7 @@
|
||||
"Phone number is invalid": "无效手机号",
|
||||
"Session outdated, please login again": "会话已过期,请重新登录",
|
||||
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
|
||||
"Username already exists": "用户名已存在",
|
||||
"Username cannot be an email address": "用户名不可以是邮箱地址",
|
||||
@ -51,6 +60,7 @@
|
||||
"Username must have at least 2 characters": "用户名至少要有2个字符",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "密码错误次数已达上限,请在 %d 分后重试",
|
||||
"Your region is not allow to signup by phone": "所在地区不支持手机号注册",
|
||||
"password or code is incorrect": "密码错误",
|
||||
"password or code is incorrect, you have %d remaining chances": "密码错误,您还有 %d 次尝试的机会",
|
||||
"unsupported password type: %s": "不支持的密码类型: %s"
|
||||
},
|
||||
@ -128,7 +138,6 @@
|
||||
"Unable to get the email modify rule.": "无法获取邮箱修改规则",
|
||||
"Unable to get the phone modify rule.": "无法获取手机号修改规则",
|
||||
"Unknown type": "未知类型",
|
||||
"Wrong parameter": "参数错误",
|
||||
"Wrong verification code!": "验证码错误!",
|
||||
"You should verify your code in %d min!": "请在 %d 分钟内输入正确验证码",
|
||||
"the user does not exist, please sign up first": "用户不存在,请先注册"
|
||||
|
30
i18n/util.go
30
i18n/util.go
@ -73,21 +73,29 @@ func applyData(data1 *I18nData, data2 *I18nData) {
|
||||
}
|
||||
}
|
||||
|
||||
func Translate(lang string, error string) string {
|
||||
parts := strings.SplitN(error, ":", 2)
|
||||
if !strings.Contains(error, ":") || len(parts) != 2 {
|
||||
return "Translate Error: " + error
|
||||
func Translate(language string, errorText string) string {
|
||||
tokens := strings.SplitN(errorText, ":", 2)
|
||||
if !strings.Contains(errorText, ":") || len(tokens) != 2 {
|
||||
return fmt.Sprintf("Translate error: the error text doesn't contain \":\", errorText = %s", errorText)
|
||||
}
|
||||
if langMap[lang] != nil {
|
||||
return langMap[lang][parts[0]][parts[1]]
|
||||
} else {
|
||||
file, _ := f.ReadFile("locales/" + lang + "/data.json")
|
||||
|
||||
if langMap[language] == nil {
|
||||
file, err := f.ReadFile(fmt.Sprintf("locales/%s/data.json", language))
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Translate error: the language \"%s\" is not supported, err = %s", language, err.Error())
|
||||
}
|
||||
|
||||
data := I18nData{}
|
||||
err := util.JsonToStruct(string(file), &data)
|
||||
err = util.JsonToStruct(string(file), &data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
langMap[lang] = data
|
||||
return langMap[lang][parts[0]][parts[1]]
|
||||
langMap[language] = data
|
||||
}
|
||||
|
||||
res := langMap[language][tokens[0]][tokens[1]]
|
||||
if res == "" {
|
||||
res = tokens[1]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
@ -108,8 +108,8 @@ func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
|
||||
type CasdoorUserInfo struct {
|
||||
Id string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"preferred_username"`
|
||||
Name string `json:"preferred_username,omitempty"`
|
||||
DisplayName string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"picture"`
|
||||
Status string `json:"status"`
|
||||
|
@ -61,8 +61,8 @@ func (idp *CustomIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
|
||||
type CustomUserInfo struct {
|
||||
Id string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"preferred_username"`
|
||||
Name string `json:"preferred_username,omitempty"`
|
||||
DisplayName string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"picture"`
|
||||
Status string `json:"status"`
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
@ -125,8 +126,8 @@ type DingTalkUserResponse struct {
|
||||
UnionId string `json:"unionId"`
|
||||
AvatarUrl string `json:"avatarUrl"`
|
||||
Email string `json:"email"`
|
||||
Errmsg string `json:"message"`
|
||||
Errcode string `json:"code"`
|
||||
Mobile string `json:"mobile"`
|
||||
StateCode string `json:"stateCode"`
|
||||
}
|
||||
|
||||
// GetUserInfo Use access_token to get UserInfo
|
||||
@ -156,8 +157,9 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dtUserInfo.Errmsg != "" {
|
||||
return nil, fmt.Errorf("userIdResp.Errcode = %s, userIdResp.Errmsg = %s", dtUserInfo.Errcode, dtUserInfo.Errmsg)
|
||||
countryCode, err := util.GetCountryCode(dtUserInfo.StateCode, dtUserInfo.Mobile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
@ -166,6 +168,8 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
||||
DisplayName: dtUserInfo.Nick,
|
||||
UnionId: dtUserInfo.UnionId,
|
||||
Email: dtUserInfo.Email,
|
||||
Phone: dtUserInfo.Mobile,
|
||||
CountryCode: countryCode,
|
||||
AvatarUrl: dtUserInfo.AvatarUrl,
|
||||
}
|
||||
|
||||
@ -175,9 +179,19 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
corpEmail, err := idp.getUserCorpEmail(userId, corpAccessToken)
|
||||
if err == nil && corpEmail != "" {
|
||||
userInfo.Email = corpEmail
|
||||
corpMobile, corpEmail, jobNumber, err := idp.getUserCorpEmail(userId, corpAccessToken)
|
||||
if err == nil {
|
||||
if corpMobile != "" {
|
||||
userInfo.Phone = corpMobile
|
||||
}
|
||||
|
||||
if corpEmail != "" {
|
||||
userInfo.Email = corpEmail
|
||||
}
|
||||
|
||||
if jobNumber != "" {
|
||||
userInfo.Username = jobNumber
|
||||
}
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
@ -247,33 +261,36 @@ func (idp *DingTalkIdProvider) getUserId(unionId string, accessToken string) (st
|
||||
return "", err
|
||||
}
|
||||
if data.ErrCode == 60121 {
|
||||
return "", fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
|
||||
return "", fmt.Errorf("该应用只允许本企业内部用户登录,您不属于该企业,无法登录")
|
||||
} else if data.ErrCode != 0 {
|
||||
return "", fmt.Errorf(data.ErrMessage)
|
||||
}
|
||||
return data.Result.UserId, nil
|
||||
}
|
||||
|
||||
func (idp *DingTalkIdProvider) getUserCorpEmail(userId string, accessToken string) (string, error) {
|
||||
func (idp *DingTalkIdProvider) getUserCorpEmail(userId string, accessToken string) (string, string, string, error) {
|
||||
// https://open.dingtalk.com/document/isvapp/query-user-details
|
||||
body := make(map[string]string)
|
||||
body["userid"] = userId
|
||||
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/v2/user/get?access_token="+accessToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
var data struct {
|
||||
ErrMessage string `json:"errmsg"`
|
||||
Result struct {
|
||||
Email string `json:"email"`
|
||||
Mobile string `json:"mobile"`
|
||||
Email string `json:"email"`
|
||||
JobNumber string `json:"job_number"`
|
||||
} `json:"result"`
|
||||
}
|
||||
err = json.Unmarshal(respBytes, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", "", err
|
||||
}
|
||||
if data.ErrMessage != "ok" {
|
||||
return "", fmt.Errorf(data.ErrMessage)
|
||||
return "", "", "", fmt.Errorf(data.ErrMessage)
|
||||
}
|
||||
return data.Result.Email, nil
|
||||
return data.Result.Mobile, data.Result.Email, data.Result.JobNumber, nil
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ type GothIdProvider struct {
|
||||
Session goth.Session
|
||||
}
|
||||
|
||||
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string) *GothIdProvider {
|
||||
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string, hostUrl string) *GothIdProvider {
|
||||
var idp GothIdProvider
|
||||
switch providerType {
|
||||
case "Amazon":
|
||||
@ -102,8 +102,13 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
||||
Session: &apple.Session{},
|
||||
}
|
||||
case "AzureAD":
|
||||
domain := "common"
|
||||
if hostUrl != "" {
|
||||
domain = hostUrl
|
||||
}
|
||||
|
||||
idp = GothIdProvider{
|
||||
Provider: azureadv2.New(clientId, clientSecret, redirectUrl, azureadv2.ProviderOptions{Tenant: "common"}),
|
||||
Provider: azureadv2.New(clientId, clientSecret, redirectUrl, azureadv2.ProviderOptions{Tenant: azureadv2.TenantType(domain)}),
|
||||
Session: &azureadv2.Session{},
|
||||
}
|
||||
case "Auth0":
|
||||
|
@ -27,6 +27,8 @@ type UserInfo struct {
|
||||
DisplayName string
|
||||
UnionId string
|
||||
Email string
|
||||
Phone string
|
||||
CountryCode string
|
||||
AvatarUrl string
|
||||
}
|
||||
|
||||
@ -90,7 +92,7 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
||||
} else if typ == "Douyin" {
|
||||
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if isGothSupport(typ) {
|
||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl, hostUrl)
|
||||
} else if typ == "Bilibili" {
|
||||
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
@ -159,8 +159,8 @@
|
||||
"serverName": "",
|
||||
"host": "",
|
||||
"port": 389,
|
||||
"admin": "",
|
||||
"passwd": "",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"baseDn": "",
|
||||
"autoSync": 0,
|
||||
"lastSync": ""
|
||||
|
124
ldap/server.go
Normal file
124
ldap/server.go
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright 2022 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 ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
ldap "github.com/forestmgy/ldapserver"
|
||||
"github.com/lor00x/goldap/message"
|
||||
)
|
||||
|
||||
func StartLdapServer() {
|
||||
server := ldap.NewServer()
|
||||
routes := ldap.NewRouteMux()
|
||||
|
||||
routes.Bind(handleBind)
|
||||
routes.Search(handleSearch).Label(" SEARCH****")
|
||||
|
||||
server.Handle(routes)
|
||||
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
r := m.GetBindRequest()
|
||||
res := ldap.NewBindResponse(ldap.LDAPResultSuccess)
|
||||
|
||||
if r.AuthenticationChoice() == "simple" {
|
||||
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
|
||||
if err != "" {
|
||||
log.Printf("Bind failed ,ErrMsg=%s", err)
|
||||
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
|
||||
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
bindPassword := string(r.AuthenticationSimple())
|
||||
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
|
||||
if err != "" {
|
||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
|
||||
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
if bindOrg == "built-in" || bindUser.IsGlobalAdmin {
|
||||
m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true
|
||||
} else if bindUser.IsAdmin {
|
||||
m.Client.IsOrgAdmin = true
|
||||
}
|
||||
|
||||
m.Client.IsAuthenticated = true
|
||||
m.Client.UserName = bindUsername
|
||||
m.Client.OrgName = bindOrg
|
||||
} else {
|
||||
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported)
|
||||
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess)
|
||||
if !m.Client.IsAuthenticated {
|
||||
res.SetResultCode(ldap.LDAPResultUnwillingToPerform)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
r := m.GetSearchRequest()
|
||||
if r.FilterString() == "(objectClass=*)" {
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle Stop Signal (server stop / client disconnected / Abandoned request....)
|
||||
select {
|
||||
case <-m.Done:
|
||||
log.Print("Leaving handleSearch...")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
users, code := GetFilteredUsers(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
|
||||
for _, attr := range r.Attributes() {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
||||
if string(attr) == "cn" {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
||||
}
|
||||
}
|
||||
|
||||
w.Write(e)
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
176
ldap/util.go
Normal file
176
ldap/util.go
Normal file
@ -0,0 +1,176 @@
|
||||
// Copyright 2022 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 ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/lor00x/goldap/message"
|
||||
|
||||
ldap "github.com/forestmgy/ldapserver"
|
||||
)
|
||||
|
||||
func getNameAndOrgFromDN(DN string) (string, string, string) {
|
||||
DNFields := strings.Split(DN, ",")
|
||||
params := make(map[string]string, len(DNFields))
|
||||
for _, field := range DNFields {
|
||||
if strings.Contains(field, "=") {
|
||||
k := strings.Split(field, "=")
|
||||
params[k[0]] = k[1]
|
||||
}
|
||||
}
|
||||
|
||||
if params["cn"] == "" {
|
||||
return "", "", "please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
|
||||
}
|
||||
if params["ou"] == "" {
|
||||
return params["cn"], object.CasdoorOrganization, ""
|
||||
}
|
||||
return params["cn"], params["ou"], ""
|
||||
}
|
||||
|
||||
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
||||
if !strings.Contains(baseDN, "ou=") {
|
||||
return "", "", ldap.LDAPResultInvalidDNSyntax
|
||||
}
|
||||
|
||||
name, org, _ := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
|
||||
return name, org, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
func getUsername(filter string) string {
|
||||
nameIndex := strings.Index(filter, "cn=")
|
||||
if nameIndex == -1 {
|
||||
nameIndex = strings.Index(filter, "uid=")
|
||||
if nameIndex == -1 {
|
||||
return "*"
|
||||
} else {
|
||||
nameIndex += 4
|
||||
}
|
||||
} else {
|
||||
nameIndex += 3
|
||||
}
|
||||
|
||||
var name string
|
||||
for i := nameIndex; filter[i] != ')'; i++ {
|
||||
name = name + string(filter[i])
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func stringInSlice(value string, list []string) bool {
|
||||
for _, item := range list {
|
||||
if item == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
|
||||
r := m.GetSearchRequest()
|
||||
|
||||
name, org, code := getNameAndOrgFromFilter(string(r.BaseObject()), r.FilterString())
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
return nil, code
|
||||
}
|
||||
|
||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||
if m.Client.IsGlobalAdmin && org == "*" {
|
||||
filteredUsers = object.GetGlobalUsers()
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
|
||||
filteredUsers = object.GetUsers(org)
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
} else {
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
} else {
|
||||
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
|
||||
userId := util.GetId(org, name)
|
||||
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, "en")
|
||||
if !hasPermission {
|
||||
log.Printf("ErrMsg = %v", err.Error())
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user != nil {
|
||||
filteredUsers = append(filteredUsers, user)
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(util.GetId("admin", org))
|
||||
if organization == nil {
|
||||
return nil, ldap.LDAPResultNoSuchObject
|
||||
}
|
||||
|
||||
if !stringInSlice(name, organization.Tags) {
|
||||
return nil, ldap.LDAPResultNoSuchObject
|
||||
}
|
||||
|
||||
users := object.GetUsersByTag(org, name)
|
||||
filteredUsers = append(filteredUsers, users...)
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
}
|
||||
|
||||
// get user password with hash type prefix
|
||||
// TODO not handle salt yet
|
||||
// @return {md5}5f4dcc3b5aa765d61d8327deb882cf99
|
||||
func getUserPasswordWithType(user *object.User) string {
|
||||
org := object.GetOrganizationByUser(user)
|
||||
if org.PasswordType == "" || org.PasswordType == "plain" {
|
||||
return user.Password
|
||||
}
|
||||
prefix := org.PasswordType
|
||||
if prefix == "salt" {
|
||||
prefix = "sha256"
|
||||
} else if prefix == "md5-salt" {
|
||||
prefix = "md5"
|
||||
} else if prefix == "pbkdf2-salt" {
|
||||
prefix = "pbkdf2"
|
||||
}
|
||||
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
||||
}
|
||||
|
||||
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:
|
||||
return ""
|
||||
}
|
||||
}
|
6
main.go
6
main.go
@ -23,7 +23,7 @@ import (
|
||||
_ "github.com/beego/beego/session/redis"
|
||||
"github.com/casdoor/casdoor/authz"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/controllers"
|
||||
"github.com/casdoor/casdoor/ldap"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/routers"
|
||||
@ -59,6 +59,7 @@ func main() {
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
@ -81,7 +82,8 @@ func main() {
|
||||
// logs.SetLevel(logs.LevelInformational)
|
||||
logs.SetLogFuncCall(false)
|
||||
|
||||
go controllers.StartLdapServer()
|
||||
go ldap.StartLdapServer()
|
||||
go object.ClearThroughputPerSecond()
|
||||
|
||||
beego.Run(fmt.Sprintf(":%v", port))
|
||||
}
|
||||
|
@ -201,6 +201,16 @@ func (a *Adapter) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Chat))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Message))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Product))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -72,6 +72,7 @@ type Application struct {
|
||||
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
FormCss string `xorm:"text" json:"formCss"`
|
||||
FormCssMobile string `xorm:"text" json:"formCssMobile"`
|
||||
FormOffset int `json:"formOffset"`
|
||||
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
|
||||
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
|
||||
@ -109,7 +110,7 @@ func GetApplications(owner string) []*Application {
|
||||
|
||||
func GetOrganizationApplications(owner string, organization string) []*Application {
|
||||
applications := []*Application{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
|
||||
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -131,7 +132,7 @@ func GetPaginationApplications(owner string, offset, limit int, field, value, so
|
||||
func GetPaginationOrganizationApplications(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Application {
|
||||
applications := []*Application{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&applications, &Application{Owner: owner, Organization: organization})
|
||||
err := session.Find(&applications, &Application{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -149,7 +150,7 @@ func getProviderMap(owner string) map[string]*Provider {
|
||||
UpdateProvider(provider.Owner+"/"+provider.Name, provider)
|
||||
}
|
||||
|
||||
m[provider.Name] = GetMaskedProvider(provider)
|
||||
m[provider.Name] = GetMaskedProvider(provider, true)
|
||||
}
|
||||
return m
|
||||
}
|
||||
@ -325,6 +326,12 @@ func UpdateApplication(id string, application *Application) bool {
|
||||
}
|
||||
|
||||
func AddApplication(application *Application) bool {
|
||||
if application.Owner == "" {
|
||||
application.Owner = "admin"
|
||||
}
|
||||
if application.Organization == "" {
|
||||
application.Organization = "built-in"
|
||||
}
|
||||
if application.ClientId == "" {
|
||||
application.ClientId = util.GenerateClientId()
|
||||
}
|
||||
|
@ -80,3 +80,21 @@ func DownloadAndUpload(url string, fullFilePath string, lang string) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getPermanentAvatarUrlFromBuffer(organization string, username string, fileBuffer *bytes.Buffer, ext string, upload bool) string {
|
||||
if defaultStorageProvider == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fullFilePath := fmt.Sprintf("/avatar/%s/%s%s", organization, username, ext)
|
||||
uploadedFileUrl, _ := GetUploadFileUrl(defaultStorageProvider, fullFilePath, false)
|
||||
|
||||
if upload {
|
||||
_, _, err := UploadFileSafe(defaultStorageProvider, fullFilePath, fileBuffer, "en")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return uploadedFileUrl
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
@ -37,3 +38,22 @@ func TestSyncPermanentAvatars(t *testing.T) {
|
||||
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAvatars(t *testing.T) {
|
||||
InitConfig()
|
||||
InitDefaultStorageProvider()
|
||||
proxy.InitHttpClient()
|
||||
|
||||
users := GetUsers("casdoor")
|
||||
for _, user := range users {
|
||||
if strings.HasPrefix(user.Avatar, "http") {
|
||||
continue
|
||||
}
|
||||
|
||||
updated := user.refreshAvatar()
|
||||
if updated {
|
||||
user.PermanentAvatar = "*"
|
||||
UpdateUser(user.GetId(), user, []string{"avatar"}, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
167
object/avatar_util.go
Normal file
167
object/avatar_util.go
Normal file
@ -0,0 +1,167 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/fogleman/gg"
|
||||
)
|
||||
|
||||
func hasGravatar(client *http.Client, email string) (bool, error) {
|
||||
// Clean and lowercase the email
|
||||
email = strings.TrimSpace(strings.ToLower(email))
|
||||
|
||||
// Generate MD5 hash of the email
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, email)
|
||||
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
|
||||
|
||||
// Create Gravatar URL with d=404 parameter
|
||||
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s?d=404", hashedEmail)
|
||||
|
||||
// Send a request to Gravatar
|
||||
req, err := http.NewRequest("GET", gravatarURL, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check if the user has a custom Gravatar image
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return true, nil
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, fmt.Errorf("failed to fetch gravatar image: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func getGravatarFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
|
||||
// Clean and lowercase the email
|
||||
email = strings.TrimSpace(strings.ToLower(email))
|
||||
|
||||
// Generate MD5 hash of the email
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, email)
|
||||
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
|
||||
|
||||
// Create Gravatar URL
|
||||
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s", hashedEmail)
|
||||
|
||||
// Download the image
|
||||
req, err := http.NewRequest("GET", gravatarURL, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, "", fmt.Errorf("failed to download gravatar image: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Get the content type and determine the file extension
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
fileExtension := ""
|
||||
switch contentType {
|
||||
case "image/jpeg":
|
||||
fileExtension = ".jpg"
|
||||
case "image/png":
|
||||
fileExtension = ".png"
|
||||
case "image/gif":
|
||||
fileExtension = ".gif"
|
||||
default:
|
||||
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
|
||||
}
|
||||
|
||||
// Save the image to a bytes.Buffer
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = io.Copy(buffer, resp.Body)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return buffer, fileExtension, nil
|
||||
}
|
||||
|
||||
func getColor(data []byte) color.RGBA {
|
||||
r := int(data[0]) % 256
|
||||
g := int(data[1]) % 256
|
||||
b := int(data[2]) % 256
|
||||
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
|
||||
}
|
||||
|
||||
func getIdenticonFileBuffer(username string) (*bytes.Buffer, string, error) {
|
||||
username = strings.TrimSpace(strings.ToLower(username))
|
||||
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, username)
|
||||
hashedUsername := hash.Sum(nil)
|
||||
|
||||
// Define the size of the image
|
||||
const imageSize = 420
|
||||
const cellSize = imageSize / 7
|
||||
|
||||
// Create a new image
|
||||
img := image.NewRGBA(image.Rect(0, 0, imageSize, imageSize))
|
||||
|
||||
// Create a context
|
||||
dc := gg.NewContextForRGBA(img)
|
||||
|
||||
// Set a background color
|
||||
dc.SetColor(color.RGBA{240, 240, 240, 255})
|
||||
dc.Clear()
|
||||
|
||||
// Get avatar color
|
||||
avatarColor := getColor(hashedUsername)
|
||||
|
||||
// Draw cells
|
||||
for i := 0; i < 7; i++ {
|
||||
for j := 0; j < 7; j++ {
|
||||
if (hashedUsername[i] >> uint(j) & 1) == 1 {
|
||||
dc.SetColor(avatarColor)
|
||||
dc.DrawRectangle(float64(j*cellSize), float64(i*cellSize), float64(cellSize), float64(cellSize))
|
||||
dc.Fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save image to a bytes.Buffer
|
||||
buffer := &bytes.Buffer{}
|
||||
err := png.Encode(buffer, img)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to save image: %w", err)
|
||||
}
|
||||
|
||||
return buffer, ".png", nil
|
||||
}
|
@ -46,9 +46,9 @@ type CasbinAdapter struct {
|
||||
Adapter *xormadapter.Adapter `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
func GetCasbinAdapterCount(owner, field, value string) int {
|
||||
func GetCasbinAdapterCount(owner, organization, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&CasbinAdapter{})
|
||||
count, err := session.Count(&CasbinAdapter{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -56,9 +56,9 @@ func GetCasbinAdapterCount(owner, field, value string) int {
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetCasbinAdapters(owner string) []*CasbinAdapter {
|
||||
func GetCasbinAdapters(owner string, organization string) []*CasbinAdapter {
|
||||
adapters := []*CasbinAdapter{}
|
||||
err := adapter.Engine.Where("owner = ?", owner).Find(&adapters)
|
||||
err := adapter.Engine.Where("owner = ? and organization = ?", owner, organization).Find(&adapters)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -66,10 +66,10 @@ func GetCasbinAdapters(owner string) []*CasbinAdapter {
|
||||
return adapters
|
||||
}
|
||||
|
||||
func GetPaginationCasbinAdapters(owner string, page, limit int, field, value, sort, order string) []*CasbinAdapter {
|
||||
func GetPaginationCasbinAdapters(owner, organization string, page, limit int, field, value, sort, order string) []*CasbinAdapter {
|
||||
session := GetSession(owner, page, limit, field, value, sort, order)
|
||||
adapters := []*CasbinAdapter{}
|
||||
err := session.Find(&adapters)
|
||||
err := session.Find(&adapters, &CasbinAdapter{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -55,8 +55,8 @@ func GetMaskedCerts(certs []*Cert) []*Cert {
|
||||
}
|
||||
|
||||
func GetCertCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Cert{})
|
||||
session := GetSession("", -1, -1, field, value, "", "")
|
||||
count, err := session.Where("owner = ? or owner = ? ", "admin", owner).Count(&Cert{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -66,7 +66,7 @@ func GetCertCount(owner, field, value string) int {
|
||||
|
||||
func GetCerts(owner string) []*Cert {
|
||||
certs := []*Cert{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&certs, &Cert{Owner: owner})
|
||||
err := adapter.Engine.Where("owner = ? or owner = ? ", "admin", owner).Desc("created_time").Find(&certs, &Cert{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -76,7 +76,38 @@ func GetCerts(owner string) []*Cert {
|
||||
|
||||
func GetPaginationCerts(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Cert {
|
||||
certs := []*Cert{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Where("owner = ? or owner = ? ", "admin", owner).Find(&certs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return certs
|
||||
}
|
||||
|
||||
func GetGlobalCertsCount(field, value string) int {
|
||||
session := GetSession("", -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Cert{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetGlobleCerts() []*Cert {
|
||||
certs := []*Cert{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&certs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return certs
|
||||
}
|
||||
|
||||
func GetPaginationGlobalCerts(offset, limit int, field, value, sortField, sortOrder string) []*Cert {
|
||||
certs := []*Cert{}
|
||||
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&certs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -103,6 +134,24 @@ func getCert(owner string, name string) *Cert {
|
||||
}
|
||||
}
|
||||
|
||||
func getCertByName(name string) *Cert {
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
cert := Cert{Name: name}
|
||||
existed, err := adapter.Engine.Get(&cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &cert
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetCert(id string) *Cert {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getCert(owner, name)
|
||||
@ -158,7 +207,7 @@ func (p *Cert) GetId() string {
|
||||
|
||||
func getCertByApplication(application *Application) *Cert {
|
||||
if application.Cert != "" {
|
||||
return getCert("admin", application.Cert)
|
||||
return getCertByName(application.Cert)
|
||||
} else {
|
||||
return GetDefaultCert()
|
||||
}
|
||||
|
154
object/chat.go
Normal file
154
object/chat.go
Normal file
@ -0,0 +1,154 @@
|
||||
// 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"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
type Chat struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
User1 string `xorm:"varchar(100)" json:"user1"`
|
||||
User2 string `xorm:"varchar(100)" json:"user2"`
|
||||
Users []string `xorm:"varchar(100)" json:"users"`
|
||||
MessageCount int `json:"messageCount"`
|
||||
}
|
||||
|
||||
func GetMaskedChat(chat *Chat) *Chat {
|
||||
if chat == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return chat
|
||||
}
|
||||
|
||||
func GetMaskedChats(chats []*Chat) []*Chat {
|
||||
for _, chat := range chats {
|
||||
chat = GetMaskedChat(chat)
|
||||
}
|
||||
return chats
|
||||
}
|
||||
|
||||
func GetChatCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Chat{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetChats(owner string) []*Chat {
|
||||
chats := []*Chat{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&chats, &Chat{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return chats
|
||||
}
|
||||
|
||||
func GetPaginationChats(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Chat {
|
||||
chats := []*Chat{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&chats)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return chats
|
||||
}
|
||||
|
||||
func getChat(owner string, name string) *Chat {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
chat := Chat{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&chat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &chat
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetChat(id string) *Chat {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getChat(owner, name)
|
||||
}
|
||||
|
||||
func UpdateChat(id string, chat *Chat) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getChat(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(chat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddChat(chat *Chat) bool {
|
||||
if chat.Type == "AI" && chat.User2 == "" {
|
||||
provider := getDefaultAiProvider()
|
||||
if provider != nil {
|
||||
chat.User2 = provider.Name
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(chat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteChat(chat *Chat) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{chat.Owner, chat.Name}).Delete(&Chat{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
return DeleteChatMessages(chat.Name)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Chat) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
130
object/check.go
130
object/check.go
@ -22,6 +22,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/form"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
@ -42,86 +43,86 @@ func init() {
|
||||
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||
}
|
||||
|
||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, countryCode string, affiliation string, lang string) string {
|
||||
func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string {
|
||||
if organization == nil {
|
||||
return i18n.Translate(lang, "check:Organization does not exist")
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Username") {
|
||||
if len(username) <= 1 {
|
||||
if len(form.Username) <= 1 {
|
||||
return i18n.Translate(lang, "check:Username must have at least 2 characters")
|
||||
}
|
||||
if unicode.IsDigit(rune(username[0])) {
|
||||
if unicode.IsDigit(rune(form.Username[0])) {
|
||||
return i18n.Translate(lang, "check:Username cannot start with a digit")
|
||||
}
|
||||
if util.IsEmailValid(username) {
|
||||
if util.IsEmailValid(form.Username) {
|
||||
return i18n.Translate(lang, "check:Username cannot be an email address")
|
||||
}
|
||||
if reWhiteSpace.MatchString(username) {
|
||||
if reWhiteSpace.MatchString(form.Username) {
|
||||
return i18n.Translate(lang, "check:Username cannot contain white spaces")
|
||||
}
|
||||
|
||||
if msg := CheckUsername(username, lang); msg != "" {
|
||||
if msg := CheckUsername(form.Username, lang); msg != "" {
|
||||
return msg
|
||||
}
|
||||
|
||||
if HasUserByField(organization.Name, "name", username) {
|
||||
if HasUserByField(organization.Name, "name", form.Username) {
|
||||
return i18n.Translate(lang, "check:Username already exists")
|
||||
}
|
||||
if HasUserByField(organization.Name, "email", email) {
|
||||
if HasUserByField(organization.Name, "email", form.Email) {
|
||||
return i18n.Translate(lang, "check:Email already exists")
|
||||
}
|
||||
if HasUserByField(organization.Name, "phone", phone) {
|
||||
if HasUserByField(organization.Name, "phone", form.Phone) {
|
||||
return i18n.Translate(lang, "check:Phone already exists")
|
||||
}
|
||||
}
|
||||
|
||||
if len(password) <= 5 {
|
||||
if len(form.Password) <= 5 {
|
||||
return i18n.Translate(lang, "check:Password must have at least 6 characters")
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") {
|
||||
if email == "" {
|
||||
if form.Email == "" {
|
||||
if application.IsSignupItemRequired("Email") {
|
||||
return i18n.Translate(lang, "check:Email cannot be empty")
|
||||
}
|
||||
} else {
|
||||
if HasUserByField(organization.Name, "email", email) {
|
||||
if HasUserByField(organization.Name, "email", form.Email) {
|
||||
return i18n.Translate(lang, "check:Email already exists")
|
||||
} else if !util.IsEmailValid(email) {
|
||||
} else if !util.IsEmailValid(form.Email) {
|
||||
return i18n.Translate(lang, "check:Email is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Phone") {
|
||||
if phone == "" {
|
||||
if form.Phone == "" {
|
||||
if application.IsSignupItemRequired("Phone") {
|
||||
return i18n.Translate(lang, "check:Phone cannot be empty")
|
||||
}
|
||||
} else {
|
||||
if HasUserByField(organization.Name, "phone", phone) {
|
||||
if HasUserByField(organization.Name, "phone", form.Phone) {
|
||||
return i18n.Translate(lang, "check:Phone already exists")
|
||||
} else if !util.IsPhoneAllowInRegin(countryCode, organization.CountryCodes) {
|
||||
} else if !util.IsPhoneAllowInRegin(form.CountryCode, organization.CountryCodes) {
|
||||
return i18n.Translate(lang, "check:Your region is not allow to signup by phone")
|
||||
} else if !util.IsPhoneValid(phone, countryCode) {
|
||||
} else if !util.IsPhoneValid(form.Phone, form.CountryCode) {
|
||||
return i18n.Translate(lang, "check:Phone number is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Display name") {
|
||||
if application.GetSignupItemRule("Display name") == "First, last" && (firstName != "" || lastName != "") {
|
||||
if firstName == "" {
|
||||
if application.GetSignupItemRule("Display name") == "First, last" && (form.FirstName != "" || form.LastName != "") {
|
||||
if form.FirstName == "" {
|
||||
return i18n.Translate(lang, "check:FirstName cannot be blank")
|
||||
} else if lastName == "" {
|
||||
} else if form.LastName == "" {
|
||||
return i18n.Translate(lang, "check:LastName cannot be blank")
|
||||
}
|
||||
} else {
|
||||
if displayName == "" {
|
||||
if form.Name == "" {
|
||||
return i18n.Translate(lang, "check:DisplayName cannot be blank")
|
||||
} else if application.GetSignupItemRule("Display name") == "Real name" {
|
||||
if !isValidRealName(displayName) {
|
||||
if !isValidRealName(form.Name) {
|
||||
return i18n.Translate(lang, "check:DisplayName is not valid real name")
|
||||
}
|
||||
}
|
||||
@ -129,7 +130,7 @@ func CheckUserSignup(application *Application, organization *Organization, usern
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Affiliation") {
|
||||
if affiliation == "" {
|
||||
if form.Affiliation == "" {
|
||||
return i18n.Translate(lang, "check:Affiliation cannot be blank")
|
||||
}
|
||||
}
|
||||
@ -157,10 +158,16 @@ func checkSigninErrorTimes(user *User, lang string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckPassword(user *User, password string, lang string) string {
|
||||
func CheckPassword(user *User, password string, lang string, options ...bool) string {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
// check the login error times
|
||||
if msg := checkSigninErrorTimes(user, lang); msg != "" {
|
||||
return msg
|
||||
if !enableCaptcha {
|
||||
if msg := checkSigninErrorTimes(user, lang); msg != "" {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
||||
organization := GetOrganizationByUser(user)
|
||||
@ -168,7 +175,11 @@ func CheckPassword(user *User, password string, lang string) string {
|
||||
return i18n.Translate(lang, "check:Organization does not exist")
|
||||
}
|
||||
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
passwordType := user.PasswordType
|
||||
if passwordType == "" {
|
||||
passwordType = organization.PasswordType
|
||||
}
|
||||
credManager := cred.GetCredManager(passwordType)
|
||||
if credManager != nil {
|
||||
if organization.MasterPassword != "" {
|
||||
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
||||
@ -182,35 +193,39 @@ func CheckPassword(user *User, password string, lang string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
return recordSigninErrorInfo(user, lang)
|
||||
return recordSigninErrorInfo(user, lang, enableCaptcha)
|
||||
} else {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
|
||||
}
|
||||
}
|
||||
|
||||
func checkLdapUserPassword(user *User, password string, lang string) (*User, string) {
|
||||
func checkLdapUserPassword(user *User, password string, lang string) string {
|
||||
ldaps := GetLdaps(user.Owner)
|
||||
ldapLoginSuccess := false
|
||||
hit := false
|
||||
|
||||
for _, ldapServer := range ldaps {
|
||||
conn, err := ldapServer.GetLdapConn()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
SearchFilter := fmt.Sprintf("(&(objectClass=posixAccount)(uid=%s))", user.Name)
|
||||
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, []string{}, nil)
|
||||
|
||||
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
|
||||
0, 0, false, ldapServer.buildAuthFilterString(user), []string{}, nil)
|
||||
|
||||
searchResult, err := conn.Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return nil, err.Error()
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
continue
|
||||
} else if len(searchResult.Entries) > 1 {
|
||||
return nil, i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
|
||||
}
|
||||
if len(searchResult.Entries) > 1 {
|
||||
return 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 {
|
||||
ldapLoginSuccess = true
|
||||
@ -219,12 +234,19 @@ func checkLdapUserPassword(user *User, password string, lang string) (*User, str
|
||||
}
|
||||
|
||||
if !ldapLoginSuccess {
|
||||
return nil, i18n.Translate(lang, "check:Ldap user name or password incorrect")
|
||||
if !hit {
|
||||
return "user not exist"
|
||||
}
|
||||
return i18n.Translate(lang, "check:LDAP user name or password incorrect")
|
||||
}
|
||||
return user, ""
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckUserPassword(organization string, username string, password string, lang string) (*User, string) {
|
||||
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, string) {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
user := GetUserByFields(organization, username)
|
||||
if user == nil || user.IsDeleted == true {
|
||||
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
||||
@ -236,10 +258,14 @@ func CheckUserPassword(organization string, username string, password string, la
|
||||
|
||||
if user.Ldap != "" {
|
||||
// ONLY for ldap users
|
||||
return checkLdapUserPassword(user, password, lang)
|
||||
if msg := checkLdapUserPassword(user, password, lang); msg != "" {
|
||||
if msg == "user not exist" {
|
||||
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
|
||||
}
|
||||
return nil, msg
|
||||
}
|
||||
} else {
|
||||
msg := CheckPassword(user, password, lang)
|
||||
if msg != "" {
|
||||
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" {
|
||||
return nil, msg
|
||||
}
|
||||
}
|
||||
@ -250,14 +276,20 @@ func filterField(field string) bool {
|
||||
return reFieldWhiteList.MatchString(field)
|
||||
}
|
||||
|
||||
func CheckUserPermission(requestUserId, userId, userOwner string, strict bool, lang string) (bool, error) {
|
||||
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
|
||||
if requestUserId == "" {
|
||||
return false, fmt.Errorf(i18n.Translate(lang, "general:Please login first"))
|
||||
}
|
||||
|
||||
userOwner := util.GetOwnerFromId(userId)
|
||||
|
||||
if userId != "" {
|
||||
targetUser := GetUser(userId)
|
||||
if targetUser == nil {
|
||||
if strings.HasPrefix(requestUserId, "built-in/") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf(i18n.Translate(lang, "general:The user: %s doesn't exist"), userId)
|
||||
}
|
||||
|
||||
@ -289,6 +321,10 @@ func CheckUserPermission(requestUserId, userId, userOwner string, strict bool, l
|
||||
}
|
||||
|
||||
func CheckAccessPermission(userId string, application *Application) (bool, error) {
|
||||
if userId == "built-in/admin" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
permissions := GetPermissions(application.Organization)
|
||||
allowed := true
|
||||
var err error
|
||||
@ -340,7 +376,7 @@ func CheckUsername(username string, lang string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckUpdateUser(oldUser *User, user *User, lang string) string {
|
||||
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||
if user.DisplayName == "" {
|
||||
return i18n.Translate(lang, "user:Display name cannot be empty")
|
||||
}
|
||||
@ -367,7 +403,7 @@ func CheckUpdateUser(oldUser *User, user *User, lang string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckToEnableCaptcha(application *Application) bool {
|
||||
func CheckToEnableCaptcha(application *Application, organization, username string) bool {
|
||||
if len(application.Providers) == 0 {
|
||||
return false
|
||||
}
|
||||
@ -377,6 +413,10 @@ func CheckToEnableCaptcha(application *Application) bool {
|
||||
continue
|
||||
}
|
||||
if providerItem.Provider.Category == "Captcha" {
|
||||
if providerItem.Rule == "Dynamic" {
|
||||
user := GetUserByFields(organization, username)
|
||||
return user != nil && user.SigninWrongTimes >= SigninWrongTimesLimit
|
||||
}
|
||||
return providerItem.Rule == "Always"
|
||||
}
|
||||
}
|
||||
|
@ -45,9 +45,15 @@ func resetUserSigninErrorTimes(user *User) {
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
|
||||
}
|
||||
|
||||
func recordSigninErrorInfo(user *User, lang string) string {
|
||||
func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
// increase failed login count
|
||||
user.SigninWrongTimes++
|
||||
if user.SigninWrongTimes < SigninWrongTimesLimit {
|
||||
user.SigninWrongTimes++
|
||||
}
|
||||
|
||||
if user.SigninWrongTimes >= SigninWrongTimesLimit {
|
||||
// record the latest failed login time
|
||||
@ -57,10 +63,11 @@ func recordSigninErrorInfo(user *User, lang string) string {
|
||||
// update user
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
|
||||
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
|
||||
if leftChances > 0 {
|
||||
if leftChances == 0 && enableCaptcha {
|
||||
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))
|
||||
} else if leftChances >= 0 {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
|
||||
}
|
||||
|
||||
// don't show the chance error message if the user has no chance left
|
||||
return fmt.Sprintf(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()))
|
||||
}
|
||||
|
@ -24,11 +24,9 @@ import (
|
||||
|
||||
func getDialer(provider *Provider) *gomail.Dialer {
|
||||
dialer := &gomail.Dialer{}
|
||||
dialer = gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||
if provider.Type == "SUBMAIL" {
|
||||
dialer = gomail.NewDialer(provider.Host, provider.Port, provider.AppId, provider.ClientSecret)
|
||||
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
} else {
|
||||
dialer = gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||
}
|
||||
|
||||
dialer.SSL = !provider.DisableSsl
|
||||
@ -40,14 +38,23 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
||||
dialer := getDialer(provider)
|
||||
|
||||
message := gomail.NewMessage()
|
||||
message.SetAddressHeader("From", provider.ClientId, sender)
|
||||
|
||||
fromAddress := provider.ClientId2
|
||||
if fromAddress == "" {
|
||||
fromAddress = provider.ClientId
|
||||
}
|
||||
|
||||
fromName := provider.ClientSecret2
|
||||
if fromName == "" {
|
||||
fromName = sender
|
||||
}
|
||||
|
||||
message.SetAddressHeader("From", fromAddress, fromName)
|
||||
message.SetHeader("To", dest)
|
||||
message.SetHeader("Subject", title)
|
||||
message.SetBody("text/html", content)
|
||||
|
||||
if provider.Type == "Mailtrap" {
|
||||
message.SkipUsernameCheck = true
|
||||
}
|
||||
message.SkipUsernameCheck = true
|
||||
|
||||
return dialer.DialAndSend(message)
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ func getBuiltInAccountItems() []*AccountItem {
|
||||
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
}
|
||||
@ -89,7 +90,7 @@ func initBuiltInOrganization() bool {
|
||||
CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"},
|
||||
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
|
||||
Tags: []string{},
|
||||
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi"},
|
||||
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "pt"},
|
||||
InitScore: 2000,
|
||||
AccountItems: getBuiltInAccountItems(),
|
||||
EnableSoftDeletion: false,
|
||||
@ -219,8 +220,8 @@ func initBuiltInLdap() {
|
||||
ServerName: "BuildIn LDAP Server",
|
||||
Host: "example.com",
|
||||
Port: 389,
|
||||
Admin: "cn=buildin,dc=example,dc=com",
|
||||
Passwd: "123",
|
||||
Username: "cn=buildin,dc=example,dc=com",
|
||||
Password: "123",
|
||||
BaseDn: "ou=BuildIn,dc=example,dc=com",
|
||||
AutoSync: 0,
|
||||
LastSync: "",
|
||||
|
402
object/ldap.go
402
object/ldap.go
@ -15,14 +15,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
)
|
||||
|
||||
type Ldap struct {
|
||||
@ -30,263 +23,20 @@ type Ldap struct {
|
||||
Owner string `xorm:"varchar(100)" json:"owner"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
EnableSsl bool `xorm:"bool" json:"enableSsl"`
|
||||
Admin string `xorm:"varchar(100)" json:"admin"`
|
||||
Passwd string `xorm:"varchar(100)" json:"passwd"`
|
||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `xorm:"int" json:"port"`
|
||||
EnableSsl bool `xorm:"bool" json:"enableSsl"`
|
||||
Username string `xorm:"varchar(100)" json:"username"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||
Filter string `xorm:"varchar(200)" json:"filter"`
|
||||
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
|
||||
|
||||
AutoSync int `json:"autoSync"`
|
||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
||||
}
|
||||
|
||||
type ldapConn struct {
|
||||
Conn *goldap.Conn
|
||||
IsAD bool
|
||||
}
|
||||
|
||||
//type ldapGroup struct {
|
||||
// GidNumber string
|
||||
// Cn string
|
||||
//}
|
||||
|
||||
type ldapUser struct {
|
||||
UidNumber string
|
||||
Uid string
|
||||
Cn string
|
||||
GidNumber string
|
||||
// Gcn string
|
||||
Uuid string
|
||||
Mail string
|
||||
Email string
|
||||
EmailAddress string
|
||||
TelephoneNumber string
|
||||
Mobile string
|
||||
MobileTelephoneNumber string
|
||||
RegisteredAddress string
|
||||
PostalAddress string
|
||||
}
|
||||
|
||||
type LdapRespUser struct {
|
||||
UidNumber string `json:"uidNumber"`
|
||||
Uid string `json:"uid"`
|
||||
Cn string `json:"cn"`
|
||||
GroupId string `json:"groupId"`
|
||||
// GroupName string `json:"groupName"`
|
||||
Uuid string `json:"uuid"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type ldapServerType struct {
|
||||
Vendorname string
|
||||
Vendorversion string
|
||||
IsGlobalCatalogReady string
|
||||
ForestFunctionality string
|
||||
}
|
||||
|
||||
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||
returnAnyNotEmpty := func(strs ...string) string {
|
||||
for _, str := range strs {
|
||||
if str != "" {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
res := make([]LdapRespUser, 0)
|
||||
for _, user := range users {
|
||||
res = append(res, LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
Uuid: user.Uuid,
|
||||
Email: returnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
||||
Phone: returnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
||||
Address: returnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
SearchFilter := "(objectclass=*)"
|
||||
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||
|
||||
searchReq := goldap.NewSearchRequest("",
|
||||
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
searchResult, err := Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return false, errors.New("no result")
|
||||
}
|
||||
isMicrosoft := false
|
||||
var ldapServerType ldapServerType
|
||||
for _, entry := range searchResult.Entries {
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "vendorname":
|
||||
ldapServerType.Vendorname = attribute.Values[0]
|
||||
case "vendorversion":
|
||||
ldapServerType.Vendorversion = attribute.Values[0]
|
||||
case "isGlobalCatalogReady":
|
||||
ldapServerType.IsGlobalCatalogReady = attribute.Values[0]
|
||||
case "forestFunctionality":
|
||||
ldapServerType.ForestFunctionality = attribute.Values[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
if ldapServerType.Vendorname == "" &&
|
||||
ldapServerType.Vendorversion == "" &&
|
||||
ldapServerType.IsGlobalCatalogReady == "TRUE" &&
|
||||
ldapServerType.ForestFunctionality != "" {
|
||||
isMicrosoft = true
|
||||
}
|
||||
return isMicrosoft, err
|
||||
}
|
||||
|
||||
func (ldap *Ldap) GetLdapConn() (c *ldapConn, err error) {
|
||||
var conn *goldap.Conn
|
||||
if ldap.EnableSsl {
|
||||
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), nil)
|
||||
} else {
|
||||
conn, err = goldap.Dial("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(ldap.Admin, ldap.Passwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isAD, err := isMicrosoftAD(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ldapConn{Conn: conn, IsAD: isAD}, nil
|
||||
}
|
||||
|
||||
//FIXME: The Base DN does not necessarily contain the Group
|
||||
//func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
|
||||
// SearchFilter := "(objectClass=posixGroup)"
|
||||
// SearchAttributes := []string{"cn", "gidNumber"}
|
||||
// groupMap := make(map[string]ldapGroup)
|
||||
//
|
||||
// searchReq := goldap.NewSearchRequest(baseDn,
|
||||
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
// SearchFilter, SearchAttributes, nil)
|
||||
// searchResult, err := l.Conn.Search(searchReq)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if len(searchResult.Entries) == 0 {
|
||||
// return nil, errors.New("no result")
|
||||
// }
|
||||
//
|
||||
// for _, entry := range searchResult.Entries {
|
||||
// var ldapGroupItem ldapGroup
|
||||
// for _, attribute := range entry.Attributes {
|
||||
// switch attribute.Name {
|
||||
// case "gidNumber":
|
||||
// ldapGroupItem.GidNumber = attribute.Values[0]
|
||||
// break
|
||||
// case "cn":
|
||||
// ldapGroupItem.Cn = attribute.Values[0]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
|
||||
// }
|
||||
//
|
||||
// return groupMap, nil
|
||||
//}
|
||||
|
||||
func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
||||
SearchFilter := "(objectClass=posixAccount)"
|
||||
SearchAttributes := []string{
|
||||
"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
|
||||
}
|
||||
SearchFilterMsAD := "(objectClass=user)"
|
||||
SearchAttributesMsAD := []string{
|
||||
"uidNumber", "sAMAccountName", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
|
||||
}
|
||||
var searchReq *goldap.SearchRequest
|
||||
if l.IsAD {
|
||||
searchReq = goldap.NewSearchRequest(baseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilterMsAD, SearchAttributesMsAD, nil)
|
||||
} else {
|
||||
searchReq = goldap.NewSearchRequest(baseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
}
|
||||
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return nil, errors.New("no result")
|
||||
}
|
||||
|
||||
var ldapUsers []ldapUser
|
||||
|
||||
for _, entry := range searchResult.Entries {
|
||||
var ldapUserItem ldapUser
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "uidNumber":
|
||||
ldapUserItem.UidNumber = attribute.Values[0]
|
||||
case "uid":
|
||||
ldapUserItem.Uid = attribute.Values[0]
|
||||
case "sAMAccountName":
|
||||
ldapUserItem.Uid = attribute.Values[0]
|
||||
case "cn":
|
||||
ldapUserItem.Cn = attribute.Values[0]
|
||||
case "gidNumber":
|
||||
ldapUserItem.GidNumber = attribute.Values[0]
|
||||
case "entryUUID":
|
||||
ldapUserItem.Uuid = attribute.Values[0]
|
||||
case "objectGUID":
|
||||
ldapUserItem.Uuid = attribute.Values[0]
|
||||
case "mail":
|
||||
ldapUserItem.Mail = attribute.Values[0]
|
||||
case "email":
|
||||
ldapUserItem.Email = attribute.Values[0]
|
||||
case "emailAddress":
|
||||
ldapUserItem.EmailAddress = attribute.Values[0]
|
||||
case "telephoneNumber":
|
||||
ldapUserItem.TelephoneNumber = attribute.Values[0]
|
||||
case "mobile":
|
||||
ldapUserItem.Mobile = attribute.Values[0]
|
||||
case "mobileTelephoneNumber":
|
||||
ldapUserItem.MobileTelephoneNumber = attribute.Values[0]
|
||||
case "registeredAddress":
|
||||
ldapUserItem.RegisteredAddress = attribute.Values[0]
|
||||
case "postalAddress":
|
||||
ldapUserItem.PostalAddress = attribute.Values[0]
|
||||
}
|
||||
}
|
||||
ldapUsers = append(ldapUsers, ldapUserItem)
|
||||
}
|
||||
|
||||
return ldapUsers, nil
|
||||
}
|
||||
|
||||
func AddLdap(ldap *Ldap) bool {
|
||||
if len(ldap.Id) == 0 {
|
||||
ldap.Id = util.GenerateId()
|
||||
@ -307,12 +57,12 @@ func AddLdap(ldap *Ldap) bool {
|
||||
func CheckLdapExist(ldap *Ldap) bool {
|
||||
var result []*Ldap
|
||||
err := adapter.Engine.Find(&result, &Ldap{
|
||||
Owner: ldap.Owner,
|
||||
Host: ldap.Host,
|
||||
Port: ldap.Port,
|
||||
Admin: ldap.Admin,
|
||||
Passwd: ldap.Passwd,
|
||||
BaseDn: ldap.BaseDn,
|
||||
Owner: ldap.Owner,
|
||||
Host: ldap.Host,
|
||||
Port: ldap.Port,
|
||||
Username: ldap.Username,
|
||||
Password: ldap.Password,
|
||||
BaseDn: ldap.BaseDn,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -359,7 +109,7 @@ func UpdateLdap(ldap *Ldap) bool {
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||
"port", "enable_ssl", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
|
||||
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync").Update(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -375,123 +125,3 @@ func DeleteLdap(ldap *Ldap) bool {
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func SyncLdapUsers(owner string, users []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
|
||||
var existUsers []LdapRespUser
|
||||
var failedUsers []LdapRespUser
|
||||
var uuids []string
|
||||
|
||||
for _, user := range users {
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
existUuids := CheckLdapUuidExist(owner, uuids)
|
||||
|
||||
organization := getOrganization("admin", owner)
|
||||
ldap := GetLdap(ldapId)
|
||||
|
||||
var dc []string
|
||||
for _, basedn := range strings.Split(ldap.BaseDn, ",") {
|
||||
if strings.Contains(basedn, "dc=") {
|
||||
dc = append(dc, basedn[3:])
|
||||
}
|
||||
}
|
||||
affiliation := strings.Join(dc, ".")
|
||||
|
||||
var ou []string
|
||||
for _, admin := range strings.Split(ldap.Admin, ",") {
|
||||
if strings.Contains(admin, "ou=") {
|
||||
ou = append(ou, admin[3:])
|
||||
}
|
||||
}
|
||||
tag := strings.Join(ou, ".")
|
||||
|
||||
for _, user := range users {
|
||||
found := false
|
||||
if len(existUuids) > 0 {
|
||||
for _, existUuid := range existUuids {
|
||||
if user.Uuid == existUuid {
|
||||
existUsers = append(existUsers, user)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found && !AddUser(&User{
|
||||
Owner: owner,
|
||||
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: user.Cn,
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Address: []string{user.Address},
|
||||
Affiliation: affiliation,
|
||||
Tag: tag,
|
||||
Score: beego.AppConfig.DefaultInt("initScore", 2000),
|
||||
Ldap: user.Uuid,
|
||||
}) {
|
||||
failedUsers = append(failedUsers, user)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return &existUsers, &failedUsers
|
||||
}
|
||||
|
||||
func UpdateLdapSyncTime(ldapId string) {
|
||||
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckLdapUuidExist(owner string, uuids []string) []string {
|
||||
var results []User
|
||||
var existUuids []string
|
||||
existUuidSet := make(map[string]struct{})
|
||||
|
||||
//whereStr := ""
|
||||
//for i, uuid := range uuids {
|
||||
// if i == 0 {
|
||||
// whereStr = fmt.Sprintf("'%s'", uuid)
|
||||
// } else {
|
||||
// whereStr = fmt.Sprintf(",'%s'", uuid)
|
||||
// }
|
||||
//}
|
||||
|
||||
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&results)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(results) > 0 {
|
||||
for _, result := range results {
|
||||
existUuidSet[result.Ldap] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for uuid := range existUuidSet {
|
||||
existUuids = append(existUuids, uuid)
|
||||
}
|
||||
return existUuids
|
||||
}
|
||||
|
||||
func buildLdapUserName(uid, uidNum string) string {
|
||||
var result User
|
||||
uidWithNumber := fmt.Sprintf("%s_%s", uid, uidNum)
|
||||
|
||||
has, err := adapter.Engine.Where("name = ? or name = ?", uid, uidWithNumber).Get(&result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
if result.Name == uid {
|
||||
return uidWithNumber
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
|
||||
}
|
||||
|
||||
return uid
|
||||
}
|
||||
|
@ -82,16 +82,18 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
||||
continue
|
||||
}
|
||||
|
||||
users, err := conn.GetLdapUsers(ldap.BaseDn)
|
||||
users, err := conn.GetLdapUsers(ldap)
|
||||
if err != nil {
|
||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||
continue
|
||||
}
|
||||
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users), ldap.Id)
|
||||
if len(*failed) != 0 {
|
||||
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
|
||||
|
||||
existed, failed, err := SyncLdapUsers(ldap.Owner, AutoAdjustLdapUser(users), ldap.Id)
|
||||
if len(failed) != 0 {
|
||||
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(existed)-len(failed), len(failed)), failed)
|
||||
logs.Warning(err.Error())
|
||||
} else {
|
||||
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(*existed), len(*existed)))
|
||||
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,3 +114,10 @@ func (l *LdapAutoSynchronizer) LdapAutoSynchronizerStartUpAll() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateLdapSyncTime(ldapId string) {
|
||||
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
397
object/ldap_conn.go
Normal file
397
object/ldap_conn.go
Normal file
@ -0,0 +1,397 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
)
|
||||
|
||||
type LdapConn struct {
|
||||
Conn *goldap.Conn
|
||||
IsAD bool
|
||||
}
|
||||
|
||||
//type ldapGroup struct {
|
||||
// GidNumber string
|
||||
// Cn string
|
||||
//}
|
||||
|
||||
type LdapUser struct {
|
||||
UidNumber string `json:"uidNumber"`
|
||||
Uid string `json:"uid"`
|
||||
Cn string `json:"cn"`
|
||||
GidNumber string `json:"gidNumber"`
|
||||
// Gcn string
|
||||
Uuid string `json:"uuid"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Mail string
|
||||
Email string `json:"email"`
|
||||
EmailAddress string
|
||||
TelephoneNumber string
|
||||
Mobile string
|
||||
MobileTelephoneNumber string
|
||||
RegisteredAddress string
|
||||
PostalAddress string
|
||||
|
||||
GroupId string `json:"groupId"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
|
||||
var conn *goldap.Conn
|
||||
if ldap.EnableSsl {
|
||||
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), nil)
|
||||
} else {
|
||||
conn, err = goldap.Dial("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(ldap.Username, ldap.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isAD, err := isMicrosoftAD(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &LdapConn{Conn: conn, IsAD: isAD}, nil
|
||||
}
|
||||
|
||||
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
SearchFilter := "(objectClass=*)"
|
||||
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||
|
||||
searchReq := goldap.NewSearchRequest("",
|
||||
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
searchResult, err := Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
isMicrosoft := false
|
||||
|
||||
type ldapServerType struct {
|
||||
Vendorname string
|
||||
Vendorversion string
|
||||
IsGlobalCatalogReady string
|
||||
ForestFunctionality string
|
||||
}
|
||||
var ldapServerTypes ldapServerType
|
||||
for _, entry := range searchResult.Entries {
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "vendorname":
|
||||
ldapServerTypes.Vendorname = attribute.Values[0]
|
||||
case "vendorversion":
|
||||
ldapServerTypes.Vendorversion = attribute.Values[0]
|
||||
case "isGlobalCatalogReady":
|
||||
ldapServerTypes.IsGlobalCatalogReady = attribute.Values[0]
|
||||
case "forestFunctionality":
|
||||
ldapServerTypes.ForestFunctionality = attribute.Values[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
if ldapServerTypes.Vendorname == "" &&
|
||||
ldapServerTypes.Vendorversion == "" &&
|
||||
ldapServerTypes.IsGlobalCatalogReady == "TRUE" &&
|
||||
ldapServerTypes.ForestFunctionality != "" {
|
||||
isMicrosoft = true
|
||||
}
|
||||
return isMicrosoft, err
|
||||
}
|
||||
|
||||
func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]LdapUser, error) {
|
||||
SearchAttributes := []string{
|
||||
"uidNumber", "cn", "sn", "gidNumber", "entryUUID", "displayName", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
|
||||
}
|
||||
if l.IsAD {
|
||||
SearchAttributes = append(SearchAttributes, "sAMAccountName")
|
||||
} else {
|
||||
SearchAttributes = append(SearchAttributes, "uid")
|
||||
}
|
||||
|
||||
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
|
||||
0, 0, false,
|
||||
ldapServer.Filter, SearchAttributes, nil)
|
||||
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return nil, errors.New("no result")
|
||||
}
|
||||
|
||||
var ldapUsers []LdapUser
|
||||
for _, entry := range searchResult.Entries {
|
||||
var user LdapUser
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "uidNumber":
|
||||
user.UidNumber = attribute.Values[0]
|
||||
case "uid":
|
||||
user.Uid = attribute.Values[0]
|
||||
case "sAMAccountName":
|
||||
user.Uid = attribute.Values[0]
|
||||
case "cn":
|
||||
user.Cn = attribute.Values[0]
|
||||
case "gidNumber":
|
||||
user.GidNumber = attribute.Values[0]
|
||||
case "entryUUID":
|
||||
user.Uuid = attribute.Values[0]
|
||||
case "objectGUID":
|
||||
user.Uuid = attribute.Values[0]
|
||||
case "displayName":
|
||||
user.DisplayName = attribute.Values[0]
|
||||
case "mail":
|
||||
user.Mail = attribute.Values[0]
|
||||
case "email":
|
||||
user.Email = attribute.Values[0]
|
||||
case "emailAddress":
|
||||
user.EmailAddress = attribute.Values[0]
|
||||
case "telephoneNumber":
|
||||
user.TelephoneNumber = attribute.Values[0]
|
||||
case "mobile":
|
||||
user.Mobile = attribute.Values[0]
|
||||
case "mobileTelephoneNumber":
|
||||
user.MobileTelephoneNumber = attribute.Values[0]
|
||||
case "registeredAddress":
|
||||
user.RegisteredAddress = attribute.Values[0]
|
||||
case "postalAddress":
|
||||
user.PostalAddress = attribute.Values[0]
|
||||
}
|
||||
}
|
||||
ldapUsers = append(ldapUsers, user)
|
||||
}
|
||||
|
||||
return ldapUsers, nil
|
||||
}
|
||||
|
||||
// FIXME: The Base DN does not necessarily contain the Group
|
||||
//
|
||||
// func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
|
||||
// SearchFilter := "(objectClass=posixGroup)"
|
||||
// SearchAttributes := []string{"cn", "gidNumber"}
|
||||
// groupMap := make(map[string]ldapGroup)
|
||||
//
|
||||
// searchReq := goldap.NewSearchRequest(baseDn,
|
||||
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
// SearchFilter, SearchAttributes, nil)
|
||||
// searchResult, err := l.Conn.Search(searchReq)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if len(searchResult.Entries) == 0 {
|
||||
// return nil, errors.New("no result")
|
||||
// }
|
||||
//
|
||||
// for _, entry := range searchResult.Entries {
|
||||
// var ldapGroupItem ldapGroup
|
||||
// for _, attribute := range entry.Attributes {
|
||||
// switch attribute.Name {
|
||||
// case "gidNumber":
|
||||
// ldapGroupItem.GidNumber = attribute.Values[0]
|
||||
// break
|
||||
// case "cn":
|
||||
// ldapGroupItem.Cn = attribute.Values[0]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
|
||||
// }
|
||||
//
|
||||
// return groupMap, nil
|
||||
// }
|
||||
|
||||
func AutoAdjustLdapUser(users []LdapUser) []LdapUser {
|
||||
res := make([]LdapUser, len(users))
|
||||
for i, user := range users {
|
||||
res[i] = LdapUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
Uuid: user.GetLdapUuid(),
|
||||
DisplayName: user.DisplayName,
|
||||
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
||||
Mobile: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
||||
RegisteredAddress: util.ReturnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUsers []LdapUser, failedUsers []LdapUser, err error) {
|
||||
var uuids []string
|
||||
for _, user := range syncUsers {
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
organization := getOrganization("admin", owner)
|
||||
ldap := GetLdap(ldapId)
|
||||
|
||||
var dc []string
|
||||
for _, basedn := range strings.Split(ldap.BaseDn, ",") {
|
||||
if strings.Contains(basedn, "dc=") {
|
||||
dc = append(dc, basedn[3:])
|
||||
}
|
||||
}
|
||||
affiliation := strings.Join(dc, ".")
|
||||
|
||||
var ou []string
|
||||
for _, admin := range strings.Split(ldap.Username, ",") {
|
||||
if strings.Contains(admin, "ou=") {
|
||||
ou = append(ou, admin[3:])
|
||||
}
|
||||
}
|
||||
tag := strings.Join(ou, ".")
|
||||
|
||||
for _, syncUser := range syncUsers {
|
||||
existUuids := GetExistUuids(owner, uuids)
|
||||
found := false
|
||||
if len(existUuids) > 0 {
|
||||
for _, existUuid := range existUuids {
|
||||
if syncUser.Uuid == existUuid {
|
||||
existUsers = append(existUsers, syncUser)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
score, _ := organization.GetInitScore()
|
||||
newUser := &User{
|
||||
Owner: owner,
|
||||
Name: syncUser.buildLdapUserName(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: syncUser.buildLdapDisplayName(),
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: syncUser.Email,
|
||||
Phone: syncUser.Phone,
|
||||
Address: []string{syncUser.Address},
|
||||
Affiliation: affiliation,
|
||||
Tag: tag,
|
||||
Score: score,
|
||||
Ldap: syncUser.Uuid,
|
||||
}
|
||||
|
||||
affected := AddUser(newUser)
|
||||
if !affected {
|
||||
failedUsers = append(failedUsers, syncUser)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return existUsers, failedUsers, err
|
||||
}
|
||||
|
||||
func GetExistUuids(owner string, uuids []string) []string {
|
||||
var existUuids []string
|
||||
|
||||
err := adapter.Engine.Table("user").Where("owner = ?", owner).Cols("ldap").
|
||||
In("ldap", uuids).Select("DISTINCT ldap").Find(&existUuids)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return existUuids
|
||||
}
|
||||
|
||||
func (ldapUser *LdapUser) buildLdapUserName() string {
|
||||
user := User{}
|
||||
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
|
||||
has, err := adapter.Engine.Where("name = ? or name = ?", ldapUser.Uid, uidWithNumber).Get(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
if user.Name == ldapUser.Uid {
|
||||
return uidWithNumber
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
|
||||
}
|
||||
|
||||
if ldapUser.Uid != "" {
|
||||
return ldapUser.Uid
|
||||
}
|
||||
|
||||
return ldapUser.Cn
|
||||
}
|
||||
|
||||
func (ldapUser *LdapUser) buildLdapDisplayName() string {
|
||||
if ldapUser.DisplayName != "" {
|
||||
return ldapUser.DisplayName
|
||||
}
|
||||
|
||||
return ldapUser.Cn
|
||||
}
|
||||
|
||||
func (ldapUser *LdapUser) GetLdapUuid() string {
|
||||
if ldapUser.Uuid != "" {
|
||||
return ldapUser.Uuid
|
||||
}
|
||||
if ldapUser.Uid != "" {
|
||||
return ldapUser.Uid
|
||||
}
|
||||
|
||||
return ldapUser.Cn
|
||||
}
|
||||
|
||||
func (ldap *Ldap) buildAuthFilterString(user *User) string {
|
||||
if len(ldap.FilterFields) == 0 {
|
||||
return fmt.Sprintf("(&%s(uid=%s))", ldap.Filter, user.Name)
|
||||
}
|
||||
|
||||
filter := fmt.Sprintf("(&%s(|", ldap.Filter)
|
||||
for _, field := range ldap.FilterFields {
|
||||
filter = fmt.Sprintf("%s(%s=%s)", filter, field, user.getFieldFromLdapAttribute(field))
|
||||
}
|
||||
filter = fmt.Sprintf("%s))", filter)
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
func (user *User) getFieldFromLdapAttribute(attribute string) string {
|
||||
switch attribute {
|
||||
case "uid":
|
||||
return user.Name
|
||||
case "sAMAccountName":
|
||||
return user.Name
|
||||
case "mail":
|
||||
return user.Email
|
||||
case "mobile":
|
||||
return user.Phone
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
// Copyright 2022 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"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/forestmgy/ldapserver"
|
||||
)
|
||||
|
||||
func GetNameAndOrgFromDN(DN string) (string, string, string) {
|
||||
DNValue := strings.Split(DN, ",")
|
||||
if len(DNValue) == 1 || strings.ToLower(DNValue[0])[0] != 'c' || strings.ToLower(DNValue[1])[0] != 'o' {
|
||||
return "", "", "please use correct Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
|
||||
}
|
||||
return DNValue[0][3:], DNValue[1][3:], ""
|
||||
}
|
||||
|
||||
func GetUserNameAndOrgFromBaseDnAndFilter(baseDN, filter string) (string, string, int) {
|
||||
if !strings.Contains(baseDN, "ou=") || !strings.Contains(filter, "cn=") {
|
||||
return "", "", ldapserver.LDAPResultInvalidDNSyntax
|
||||
}
|
||||
name := getUserNameFromFilter(filter)
|
||||
_, org, _ := GetNameAndOrgFromDN(fmt.Sprintf("cn=%s,", name) + baseDN)
|
||||
errCode := ldapserver.LDAPResultSuccess
|
||||
return name, org, errCode
|
||||
}
|
||||
|
||||
func getUserNameFromFilter(filter string) string {
|
||||
nameIndex := strings.Index(filter, "cn=")
|
||||
var name string
|
||||
for i := nameIndex + 3; filter[i] != ')'; i++ {
|
||||
name = name + string(filter[i])
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func GetFilteredUsers(m *ldapserver.Message, name, org string) ([]*User, int) {
|
||||
var filteredUsers []*User
|
||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||
if m.Client.OrgName == "built-in" && org == "*" {
|
||||
filteredUsers = GetGlobalUsers()
|
||||
return filteredUsers, ldapserver.LDAPResultSuccess
|
||||
} else if m.Client.OrgName == "built-in" || org == m.Client.OrgName {
|
||||
filteredUsers = GetUsers(org)
|
||||
return filteredUsers, ldapserver.LDAPResultSuccess
|
||||
} else {
|
||||
return nil, ldapserver.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
} else {
|
||||
hasPermission, err := CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), org, true, "en")
|
||||
if !hasPermission {
|
||||
log.Printf("ErrMsg = %v", err.Error())
|
||||
return nil, ldapserver.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
user := getUser(org, name)
|
||||
filteredUsers = append(filteredUsers, user)
|
||||
return filteredUsers, ldapserver.LDAPResultSuccess
|
||||
}
|
||||
}
|
158
object/message.go
Normal file
158
object/message.go
Normal file
@ -0,0 +1,158 @@
|
||||
// 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"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Chat string `xorm:"varchar(100) index" json:"chat"`
|
||||
ReplyTo string `xorm:"varchar(100) index" json:"replyTo"`
|
||||
Author string `xorm:"varchar(100)" json:"author"`
|
||||
Text string `xorm:"mediumtext" json:"text"`
|
||||
}
|
||||
|
||||
func GetMaskedMessage(message *Message) *Message {
|
||||
if message == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func GetMaskedMessages(messages []*Message) []*Message {
|
||||
for _, message := range messages {
|
||||
message = GetMaskedMessage(message)
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
||||
func GetMessageCount(owner, organization, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Message{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetMessages(owner string) []*Message {
|
||||
messages := []*Message{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&messages, &Message{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
func GetChatMessages(chat string) []*Message {
|
||||
messages := []*Message{}
|
||||
err := adapter.Engine.Asc("created_time").Find(&messages, &Message{Chat: chat})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
func GetPaginationMessages(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Message {
|
||||
messages := []*Message{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&messages, &Message{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
func getMessage(owner string, name string) *Message {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
message := Message{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &message
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetMessage(id string) *Message {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getMessage(owner, name)
|
||||
}
|
||||
|
||||
func UpdateMessage(id string, message *Message) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getMessage(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddMessage(message *Message) bool {
|
||||
affected, err := adapter.Engine.Insert(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteMessage(message *Message) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{message.Owner, message.Name}).Delete(&Message{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteChatMessages(chat string) bool {
|
||||
affected, err := adapter.Engine.Delete(&Message{Chat: chat})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Message) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
108
object/mfa.go
Normal file
108
object/mfa.go
Normal file
@ -0,0 +1,108 @@
|
||||
// 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"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
)
|
||||
|
||||
type MfaSessionData struct {
|
||||
UserId string
|
||||
}
|
||||
|
||||
type MfaProps struct {
|
||||
Id string `json:"id"`
|
||||
IsPreferred bool `json:"isPreferred"`
|
||||
AuthType string `json:"type" form:"type"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
CountryCode string `json:"countryCode,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
RecoveryCodes []string `json:"recoveryCodes,omitempty"`
|
||||
}
|
||||
|
||||
type MfaInterface interface {
|
||||
SetupVerify(ctx *context.Context, passCode string) error
|
||||
Verify(passCode string) error
|
||||
Initiate(ctx *context.Context, name1 string, name2 string) (*MfaProps, error)
|
||||
Enable(ctx *context.Context, user *User) error
|
||||
}
|
||||
|
||||
const (
|
||||
SmsType = "sms"
|
||||
TotpType = "app"
|
||||
)
|
||||
|
||||
const (
|
||||
MfaSessionUserId = "MfaSessionUserId"
|
||||
NextMfa = "NextMfa"
|
||||
RequiredMfa = "RequiredMfa"
|
||||
)
|
||||
|
||||
func GetMfaUtil(providerType string, config *MfaProps) MfaInterface {
|
||||
switch providerType {
|
||||
case SmsType:
|
||||
return NewSmsTwoFactor(config)
|
||||
case TotpType:
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RecoverTfs(user *User, recoveryCode string) error {
|
||||
hit := false
|
||||
|
||||
twoFactor := user.GetPreferMfa(false)
|
||||
if len(twoFactor.RecoveryCodes) == 0 {
|
||||
return fmt.Errorf("do not have recovery codes")
|
||||
}
|
||||
|
||||
for _, code := range twoFactor.RecoveryCodes {
|
||||
if code == recoveryCode {
|
||||
hit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hit {
|
||||
return fmt.Errorf("recovery code not found")
|
||||
}
|
||||
|
||||
affected := UpdateUser(user.GetId(), user, []string{"two_factor_auth"}, user.IsAdminUser())
|
||||
if !affected {
|
||||
return fmt.Errorf("")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMaskedProps(props *MfaProps) *MfaProps {
|
||||
maskedProps := &MfaProps{
|
||||
AuthType: props.AuthType,
|
||||
Id: props.Id,
|
||||
IsPreferred: props.IsPreferred,
|
||||
}
|
||||
|
||||
if props.AuthType == SmsType {
|
||||
if !util.IsEmailValid(props.Secret) {
|
||||
maskedProps.Secret = util.GetMaskedPhone(props.Secret)
|
||||
} else {
|
||||
maskedProps.Secret = util.GetMaskedEmail(props.Secret)
|
||||
}
|
||||
}
|
||||
return maskedProps
|
||||
}
|
120
object/mfa_sms.go
Normal file
120
object/mfa_sms.go
Normal file
@ -0,0 +1,120 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
MfaSmsCountryCodeSession = "mfa_country_code"
|
||||
MfaSmsDestSession = "mfa_dest"
|
||||
MfaSmsRecoveryCodesSession = "mfa_recovery_codes"
|
||||
)
|
||||
|
||||
type SmsMfa struct {
|
||||
Config *MfaProps
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) SetupVerify(ctx *context.Context, passCode string) error {
|
||||
dest := ctx.Input.CruSession.Get(MfaSmsDestSession).(string)
|
||||
countryCode := ctx.Input.CruSession.Get(MfaSmsCountryCodeSession).(string)
|
||||
if !util.IsEmailValid(dest) {
|
||||
dest, _ = util.GetE164Number(dest, countryCode)
|
||||
}
|
||||
|
||||
if result := CheckVerificationCode(dest, passCode, "en"); result.Code != VerificationSuccess {
|
||||
return errors.New(result.Msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) Verify(passCode string) error {
|
||||
if !util.IsEmailValid(mfa.Config.Secret) {
|
||||
mfa.Config.Secret, _ = util.GetE164Number(mfa.Config.Secret, mfa.Config.CountryCode)
|
||||
}
|
||||
if result := CheckVerificationCode(mfa.Config.Secret, passCode, "en"); result.Code != VerificationSuccess {
|
||||
return errors.New(result.Msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) Initiate(ctx *context.Context, name string, secret string) (*MfaProps, error) {
|
||||
recoveryCode, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ctx.Input.CruSession.Set(MfaSmsRecoveryCodesSession, []string{recoveryCode.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mfaProps := MfaProps{
|
||||
AuthType: SmsType,
|
||||
RecoveryCodes: []string{recoveryCode.String()},
|
||||
}
|
||||
return &mfaProps, nil
|
||||
}
|
||||
|
||||
func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
|
||||
dest := ctx.Input.CruSession.Get(MfaSmsDestSession).(string)
|
||||
recoveryCodes := ctx.Input.CruSession.Get(MfaSmsRecoveryCodesSession).([]string)
|
||||
countryCode := ctx.Input.CruSession.Get(MfaSmsCountryCodeSession).(string)
|
||||
|
||||
if dest == "" || len(recoveryCodes) == 0 {
|
||||
return fmt.Errorf("MFA dest or recovery codes is empty")
|
||||
}
|
||||
|
||||
if !util.IsEmailValid(dest) {
|
||||
mfa.Config.CountryCode = countryCode
|
||||
}
|
||||
|
||||
mfa.Config.AuthType = SmsType
|
||||
mfa.Config.Id = uuid.NewString()
|
||||
mfa.Config.Secret = dest
|
||||
mfa.Config.RecoveryCodes = recoveryCodes
|
||||
|
||||
for i, mfaProp := range user.MultiFactorAuths {
|
||||
if mfaProp.Secret == mfa.Config.Secret {
|
||||
user.MultiFactorAuths = append(user.MultiFactorAuths[:i], user.MultiFactorAuths[i+1:]...)
|
||||
}
|
||||
}
|
||||
user.MultiFactorAuths = append(user.MultiFactorAuths, mfa.Config)
|
||||
|
||||
affected := UpdateUser(user.GetId(), user, []string{"multi_factor_auths"}, user.IsAdminUser())
|
||||
if !affected {
|
||||
return fmt.Errorf("failed to enable two factor authentication")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSmsTwoFactor(config *MfaProps) *SmsMfa {
|
||||
if config == nil {
|
||||
config = &MfaProps{
|
||||
AuthType: SmsType,
|
||||
}
|
||||
}
|
||||
return &SmsMfa{
|
||||
Config: config,
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ func DoMigration() {
|
||||
&Migrator_1_101_0_PR_1083{},
|
||||
&Migrator_1_235_0_PR_1530{},
|
||||
&Migrator_1_240_0_PR_1539{},
|
||||
&Migrator_1_314_0_PR_1841{},
|
||||
// more migrators add here in chronological order...
|
||||
}
|
||||
|
||||
|
75
object/migrator_1_314_0_PR_1841.go
Normal file
75
object/migrator_1_314_0_PR_1841.go
Normal file
@ -0,0 +1,75 @@
|
||||
// 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 (
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_314_0_PR_1841 struct{}
|
||||
|
||||
func (*Migrator_1_314_0_PR_1841) IsMigrationNeeded() bool {
|
||||
users := []*User{}
|
||||
|
||||
err := adapter.Engine.Table("user").Find(&users)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if u.PasswordType != "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (*Migrator_1_314_0_PR_1841) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20230515MigrateUser--Create a new field 'passwordType' for table `user`",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
tx := engine.NewSession()
|
||||
|
||||
defer tx.Close()
|
||||
|
||||
err := tx.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
organizations := []*Organization{}
|
||||
err = tx.Table("organization").Find(&organizations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, organization := range organizations {
|
||||
user := &User{PasswordType: organization.PasswordType}
|
||||
_, err = tx.Where("owner = ?", organization.Name).Cols("password_type").Update(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -18,6 +18,7 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@ -43,6 +44,26 @@ type OidcDiscovery struct {
|
||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||
}
|
||||
|
||||
func isIpAddress(host string) bool {
|
||||
// Attempt to split the host and port, ignoring the error
|
||||
hostWithoutPort, _, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
// If an error occurs, it might be because there's no port
|
||||
// In that case, use the original host string
|
||||
hostWithoutPort = host
|
||||
}
|
||||
|
||||
// Attempt to parse the host as an IP address (both IPv4 and IPv6)
|
||||
ip := net.ParseIP(hostWithoutPort)
|
||||
if ip != nil {
|
||||
// The host is an IP address
|
||||
return true
|
||||
}
|
||||
|
||||
// The host is not an IP address
|
||||
return false
|
||||
}
|
||||
|
||||
func getOriginFromHost(host string) (string, string) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
if origin != "" {
|
||||
@ -52,6 +73,8 @@ func getOriginFromHost(host string) (string, string) {
|
||||
protocol := "https://"
|
||||
if strings.HasPrefix(host, "localhost") {
|
||||
protocol = "http://"
|
||||
} else if isIpAddress(host) {
|
||||
protocol = "http://"
|
||||
}
|
||||
|
||||
if host == "localhost:8000" {
|
||||
|
@ -16,8 +16,9 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"strconv"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -39,6 +40,11 @@ type ThemeData struct {
|
||||
IsEnabled bool `xorm:"bool" json:"isEnabled"`
|
||||
}
|
||||
|
||||
type MfaItem struct {
|
||||
Name string `json:"name"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
|
||||
type Organization struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
@ -50,7 +56,7 @@ type Organization struct {
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||
@ -60,6 +66,7 @@ type Organization struct {
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
|
||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
|
||||
}
|
||||
|
||||
@ -210,14 +217,14 @@ func GetAccountItemByName(name string, organization *Organization) *AccountItem
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckAccountItemModifyRule(accountItem *AccountItem, user *User, lang string) (bool, string) {
|
||||
func CheckAccountItemModifyRule(accountItem *AccountItem, isAdmin bool, lang string) (bool, string) {
|
||||
if accountItem == nil {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
switch accountItem.ModifyRule {
|
||||
case "Admin":
|
||||
if user == nil || !user.IsAdmin && !user.IsGlobalAdmin {
|
||||
if !isAdmin {
|
||||
return false, fmt.Sprintf(i18n.Translate(lang, "organization:Only admin can modify the %s."), accountItem.Name)
|
||||
}
|
||||
case "Immutable":
|
||||
@ -299,18 +306,16 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
||||
}
|
||||
for i, u := range role.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
role.Users[i] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
role.Users[i] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
for i, u := range role.Roles {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
role.Roles[i] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
role.Roles[i] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
role.Owner = newName
|
||||
@ -326,18 +331,16 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
||||
}
|
||||
for i, u := range permission.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
permission.Users[i] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
permission.Users[i] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
for i, u := range permission.Roles {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
permission.Roles[i] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
permission.Roles[i] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
permission.Owner = newName
|
||||
@ -413,3 +416,20 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
||||
func (org *Organization) HasRequiredMfa() bool {
|
||||
for _, item := range org.MfaItems {
|
||||
if item.Rule == "Required" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (org *Organization) GetInitScore() (int, error) {
|
||||
if org != nil {
|
||||
return org.InitScore, nil
|
||||
} else {
|
||||
return strconv.Atoi(conf.GetConfigString("initScore"))
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,6 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
@ -65,30 +63,6 @@ func (p *Permission) GetId() string {
|
||||
return util.GetId(p.Owner, p.Name)
|
||||
}
|
||||
|
||||
func (p *PermissionRule) GetRequest(adapterName string, permissionId string) ([]interface{}, error) {
|
||||
request := []interface{}{p.V0, p.V1, p.V2}
|
||||
|
||||
if p.V3 != "" {
|
||||
request = append(request, p.V3)
|
||||
}
|
||||
|
||||
if p.V4 != "" {
|
||||
request = append(request, p.V4)
|
||||
}
|
||||
|
||||
if adapterName == builtInAdapter {
|
||||
if p.V5 != "" {
|
||||
return nil, fmt.Errorf("too many parameters. The maximum parameter number cannot exceed %d", builtInAvailableField)
|
||||
}
|
||||
return request, nil
|
||||
} else {
|
||||
if p.V5 != "" {
|
||||
request = append(request, p.V5)
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetPermissionCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Permission{})
|
||||
@ -239,7 +213,7 @@ func DeletePermission(permission *Permission) bool {
|
||||
|
||||
func GetPermissionsByUser(userId string) []*Permission {
|
||||
permissions := []*Permission{}
|
||||
err := adapter.Engine.Where("users like ?", "%"+userId+"%").Find(&permissions)
|
||||
err := adapter.Engine.Where("users like ?", "%"+userId+"\"%").Find(&permissions)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -253,7 +227,17 @@ func GetPermissionsByUser(userId string) []*Permission {
|
||||
|
||||
func GetPermissionsByRole(roleId string) []*Permission {
|
||||
permissions := []*Permission{}
|
||||
err := adapter.Engine.Where("roles like ?", "%"+roleId+"%").Find(&permissions)
|
||||
err := adapter.Engine.Where("roles like ?", "%"+roleId+"\"%").Find(&permissions)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func GetPermissionsByResource(resourceId string) []*Permission {
|
||||
permissions := []*Permission{}
|
||||
err := adapter.Engine.Where("resources like ?", "%"+resourceId+"\"%").Find(&permissions)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -271,6 +255,16 @@ func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
|
||||
return permissions
|
||||
}
|
||||
|
||||
func GetPermissionsByModel(owner string, model string) []*Permission {
|
||||
permissions := []*Permission{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Model: model})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func ContainsAsterisk(userId string, users []string) bool {
|
||||
containsAsterisk := false
|
||||
group, _ := util.GetOwnerAndNameFromId(userId)
|
||||
|
@ -29,7 +29,10 @@ import (
|
||||
func getEnforcer(permission *Permission) *casbin.Enforcer {
|
||||
tableName := "permission_rule"
|
||||
if len(permission.Adapter) != 0 {
|
||||
tableName = permission.Adapter
|
||||
adapterObj := getCasbinAdapter(permission.Owner, permission.Adapter)
|
||||
if adapterObj != nil && adapterObj.Table != "" {
|
||||
tableName = adapterObj.Table
|
||||
}
|
||||
}
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
driverName := conf.GetConfigString("driverName")
|
||||
@ -59,7 +62,11 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
enforcer.InitWithModelAndAdapter(m, nil)
|
||||
err = enforcer.InitWithModelAndAdapter(m, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
enforcer.SetAdapter(adapter)
|
||||
|
||||
policyFilter := xormadapter.Filter{
|
||||
@ -115,35 +122,53 @@ func getPolicies(permission *Permission) [][]string {
|
||||
return policies
|
||||
}
|
||||
|
||||
func getRolesInRole(roleId string, visited map[string]struct{}) []*Role {
|
||||
role := GetRole(roleId)
|
||||
if role == nil {
|
||||
return []*Role{}
|
||||
}
|
||||
visited[roleId] = struct{}{}
|
||||
|
||||
roles := []*Role{role}
|
||||
for _, subRole := range role.Roles {
|
||||
if _, ok := visited[subRole]; !ok {
|
||||
roles = append(roles, getRolesInRole(subRole, visited)...)
|
||||
}
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
func getGroupingPolicies(permission *Permission) [][]string {
|
||||
var groupingPolicies [][]string
|
||||
|
||||
domainExist := len(permission.Domains) > 0
|
||||
permissionId := permission.GetId()
|
||||
|
||||
for _, role := range permission.Roles {
|
||||
roleObj := GetRole(role)
|
||||
if roleObj == nil {
|
||||
continue
|
||||
}
|
||||
for _, roleId := range permission.Roles {
|
||||
visited := map[string]struct{}{}
|
||||
rolesInRole := getRolesInRole(roleId, visited)
|
||||
|
||||
for _, subUser := range roleObj.Users {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, domain, role, "", "", permissionId})
|
||||
for _, role := range rolesInRole {
|
||||
roleId := role.GetId()
|
||||
for _, subUser := range role.Users {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, roleId, domain, "", "", permissionId})
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, roleId, "", "", "", permissionId})
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, role, "", "", "", permissionId})
|
||||
}
|
||||
}
|
||||
|
||||
for _, subRole := range roleObj.Roles {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, domain, role, "", "", permissionId})
|
||||
for _, subRole := range role.Roles {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, roleId, domain, "", "", permissionId})
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, roleId, "", "", "", permissionId})
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, role, "", "", "", permissionId})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -195,28 +220,23 @@ func removePolicies(permission *Permission) {
|
||||
}
|
||||
}
|
||||
|
||||
func Enforce(permissionRule *PermissionRule) bool {
|
||||
permission := GetPermission(permissionRule.Id)
|
||||
type CasbinRequest = []interface{}
|
||||
|
||||
func Enforce(permissionId string, request *CasbinRequest) bool {
|
||||
permission := GetPermission(permissionId)
|
||||
enforcer := getEnforcer(permission)
|
||||
|
||||
request, _ := permissionRule.GetRequest(builtInAdapter, permissionRule.Id)
|
||||
|
||||
allow, err := enforcer.Enforce(request...)
|
||||
allow, err := enforcer.Enforce(*request...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return allow
|
||||
}
|
||||
|
||||
func BatchEnforce(permissionRules []PermissionRule) []bool {
|
||||
var requests [][]interface{}
|
||||
for _, permissionRule := range permissionRules {
|
||||
request, _ := permissionRule.GetRequest(builtInAdapter, permissionRule.Id)
|
||||
requests = append(requests, request)
|
||||
}
|
||||
permission := GetPermission(permissionRules[0].Id)
|
||||
func BatchEnforce(permissionId string, requests *[]CasbinRequest) []bool {
|
||||
permission := GetPermission(permissionId)
|
||||
enforcer := getEnforcer(permission)
|
||||
allow, err := enforcer.BatchEnforce(requests)
|
||||
allow, err := enforcer.BatchEnforce(*requests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -30,7 +30,10 @@ func TestProduct(t *testing.T) {
|
||||
product := GetProduct("admin/product_123")
|
||||
provider := getProvider(product.Owner, "provider_pay_alipay")
|
||||
cert := getCert(product.Owner, "cert-pay-alipay")
|
||||
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||
pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
paymentName := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
|
129
object/prometheus.go
Normal file
129
object/prometheus.go
Normal file
@ -0,0 +1,129 @@
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
type PrometheusInfo struct {
|
||||
ApiThroughput []GaugeVecInfo `json:"apiThroughput"`
|
||||
ApiLatency []HistogramVecInfo `json:"apiLatency"`
|
||||
TotalThroughput float64 `json:"totalThroughput"`
|
||||
}
|
||||
|
||||
type GaugeVecInfo struct {
|
||||
Method string `json:"method"`
|
||||
Name string `json:"name"`
|
||||
Throughput float64 `json:"throughput"`
|
||||
}
|
||||
|
||||
type HistogramVecInfo struct {
|
||||
Method string `json:"method"`
|
||||
Name string `json:"name"`
|
||||
Count uint64 `json:"count"`
|
||||
Latency string `json:"latency"`
|
||||
}
|
||||
|
||||
var (
|
||||
ApiThroughput = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "casdoor_api_throughput",
|
||||
Help: "The throughput of each api access",
|
||||
}, []string{"path", "method"})
|
||||
|
||||
ApiLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "casdoor_api_latency",
|
||||
Help: "API processing latency in milliseconds",
|
||||
}, []string{"path", "method"})
|
||||
|
||||
CpuUsage = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "casdoor_cpu_usage",
|
||||
Help: "Casdoor cpu usage",
|
||||
}, []string{"cpuNum"})
|
||||
|
||||
MemoryUsage = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "casdoor_memory_usage",
|
||||
Help: "Casdoor memory usage in Byte",
|
||||
}, []string{"type"})
|
||||
|
||||
TotalThroughput = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "casdoor_total_throughput",
|
||||
Help: "The total throughput of casdoor",
|
||||
})
|
||||
)
|
||||
|
||||
func ClearThroughputPerSecond() {
|
||||
// Clear the throughput every second
|
||||
ticker := time.NewTicker(time.Second)
|
||||
for range ticker.C {
|
||||
ApiThroughput.Reset()
|
||||
TotalThroughput.Set(0)
|
||||
}
|
||||
}
|
||||
|
||||
func GetPrometheusInfo() (*PrometheusInfo, error) {
|
||||
res := &PrometheusInfo{}
|
||||
metricFamilies, err := prometheus.DefaultGatherer.Gather()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, metricFamily := range metricFamilies {
|
||||
switch metricFamily.GetName() {
|
||||
case "casdoor_api_throughput":
|
||||
res.ApiThroughput = getGaugeVecInfo(metricFamily)
|
||||
case "casdoor_api_latency":
|
||||
res.ApiLatency = getHistogramVecInfo(metricFamily)
|
||||
case "casdoor_total_throughput":
|
||||
res.TotalThroughput = metricFamily.GetMetric()[0].GetGauge().GetValue()
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getHistogramVecInfo(metricFamily *io_prometheus_client.MetricFamily) []HistogramVecInfo {
|
||||
var histogramVecInfos []HistogramVecInfo
|
||||
for _, metric := range metricFamily.GetMetric() {
|
||||
sampleCount := metric.GetHistogram().GetSampleCount()
|
||||
sampleSum := metric.GetHistogram().GetSampleSum()
|
||||
latency := sampleSum / float64(sampleCount)
|
||||
histogramVecInfo := HistogramVecInfo{
|
||||
Method: metric.Label[0].GetValue(),
|
||||
Name: metric.Label[1].GetValue(),
|
||||
Count: sampleCount,
|
||||
Latency: fmt.Sprintf("%.3f", latency),
|
||||
}
|
||||
histogramVecInfos = append(histogramVecInfos, histogramVecInfo)
|
||||
}
|
||||
return histogramVecInfos
|
||||
}
|
||||
|
||||
func getGaugeVecInfo(metricFamily *io_prometheus_client.MetricFamily) []GaugeVecInfo {
|
||||
var counterVecInfos []GaugeVecInfo
|
||||
for _, metric := range metricFamily.GetMetric() {
|
||||
counterVecInfo := GaugeVecInfo{
|
||||
Method: metric.Label[0].GetValue(),
|
||||
Name: metric.Label[1].GetValue(),
|
||||
Throughput: metric.Gauge.GetValue(),
|
||||
}
|
||||
counterVecInfos = append(counterVecInfos, counterVecInfo)
|
||||
}
|
||||
return counterVecInfos
|
||||
}
|
@ -70,7 +70,11 @@ type Provider struct {
|
||||
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
||||
}
|
||||
|
||||
func GetMaskedProvider(provider *Provider) *Provider {
|
||||
func GetMaskedProvider(provider *Provider, isMaskEnabled bool) *Provider {
|
||||
if !isMaskEnabled {
|
||||
return provider
|
||||
}
|
||||
|
||||
if provider == nil {
|
||||
return nil
|
||||
}
|
||||
@ -78,16 +82,23 @@ func GetMaskedProvider(provider *Provider) *Provider {
|
||||
if provider.ClientSecret != "" {
|
||||
provider.ClientSecret = "***"
|
||||
}
|
||||
if provider.ClientSecret2 != "" {
|
||||
provider.ClientSecret2 = "***"
|
||||
|
||||
if provider.Category != "Email" {
|
||||
if provider.ClientSecret2 != "" {
|
||||
provider.ClientSecret2 = "***"
|
||||
}
|
||||
}
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func GetMaskedProviders(providers []*Provider) []*Provider {
|
||||
func GetMaskedProviders(providers []*Provider, isMaskEnabled bool) []*Provider {
|
||||
if !isMaskEnabled {
|
||||
return providers
|
||||
}
|
||||
|
||||
for _, provider := range providers {
|
||||
provider = GetMaskedProvider(provider)
|
||||
provider = GetMaskedProvider(provider, isMaskEnabled)
|
||||
}
|
||||
return providers
|
||||
}
|
||||
@ -177,8 +188,8 @@ func GetProvider(id string) *Provider {
|
||||
return getProvider(owner, name)
|
||||
}
|
||||
|
||||
func GetDefaultCaptchaProvider() *Provider {
|
||||
provider := Provider{Owner: "admin", Category: "Captcha"}
|
||||
func getDefaultAiProvider() *Provider {
|
||||
provider := Provider{Owner: "admin", Category: "AI"}
|
||||
existed, err := adapter.Engine.Get(&provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -221,6 +232,10 @@ func UpdateProvider(id string, provider *Provider) bool {
|
||||
if provider.ClientSecret2 == "***" {
|
||||
session = session.Omit("client_secret2")
|
||||
}
|
||||
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
|
||||
affected, err := session.Update(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -230,6 +245,9 @@ func UpdateProvider(id string, provider *Provider) bool {
|
||||
}
|
||||
|
||||
func AddProvider(provider *Provider) bool {
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
|
||||
affected, err := adapter.Engine.Insert(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -256,7 +274,11 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
||||
}
|
||||
}
|
||||
|
||||
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||
pProvider, err := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, p.ClientId2)
|
||||
if err != nil {
|
||||
return nil, cert, err
|
||||
}
|
||||
|
||||
if pProvider == nil {
|
||||
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||
}
|
||||
@ -296,7 +318,7 @@ func GetCaptchaProviderByApplication(applicationId, isCurrentProvider, lang stri
|
||||
continue
|
||||
}
|
||||
if provider.Provider.Category == "Captcha" {
|
||||
return GetCaptchaProviderByOwnerName(fmt.Sprintf("%s/%s", provider.Provider.Owner, provider.Provider.Name), lang)
|
||||
return GetCaptchaProviderByOwnerName(util.GetId(provider.Provider.Owner, provider.Provider.Name), lang)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
|
@ -47,7 +47,8 @@ type Record struct {
|
||||
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
|
||||
Action string `xorm:"varchar(1000)" json:"action"`
|
||||
|
||||
ExtendedUser *User `xorm:"-" json:"extendedUser"`
|
||||
Object string `xorm:"-" json:"object"`
|
||||
ExtendedUser *User `xorm:"-" json:"extendedUser"`
|
||||
|
||||
IsTriggered bool `json:"isTriggered"`
|
||||
}
|
||||
@ -60,6 +61,11 @@ func NewRecord(ctx *context.Context) *Record {
|
||||
requestUri = requestUri[0:1000]
|
||||
}
|
||||
|
||||
object := ""
|
||||
if ctx.Input.RequestBody != nil && len(ctx.Input.RequestBody) != 0 {
|
||||
object = string(ctx.Input.RequestBody)
|
||||
}
|
||||
|
||||
record := Record{
|
||||
Name: util.GenerateId(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
@ -68,6 +74,7 @@ func NewRecord(ctx *context.Context) *Record {
|
||||
Method: ctx.Request.Method,
|
||||
RequestUri: requestUri,
|
||||
Action: action,
|
||||
Object: object,
|
||||
IsTriggered: false,
|
||||
}
|
||||
return &record
|
||||
@ -159,7 +166,7 @@ func SendWebhooks(record *Record) error {
|
||||
|
||||
if matched {
|
||||
if webhook.IsUserExtended {
|
||||
user := getUser(record.Organization, record.User)
|
||||
user := GetMaskedUser(getUser(record.Organization, record.User))
|
||||
record.ExtendedUser = user
|
||||
}
|
||||
|
||||
|
111
object/role.go
111
object/role.go
@ -16,7 +16,6 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
@ -95,10 +94,25 @@ func UpdateRole(id string, role *Role) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
visited := map[string]struct{}{}
|
||||
|
||||
permissions := GetPermissionsByRole(id)
|
||||
for _, permission := range permissions {
|
||||
removeGroupingPolicies(permission)
|
||||
removePolicies(permission)
|
||||
visited[permission.GetId()] = struct{}{}
|
||||
}
|
||||
|
||||
ancestorRoles := GetAncestorRoles(id)
|
||||
for _, r := range ancestorRoles {
|
||||
permissions := GetPermissionsByRole(r.GetId())
|
||||
for _, permission := range permissions {
|
||||
permissionId := permission.GetId()
|
||||
if _, ok := visited[permissionId]; !ok {
|
||||
removeGroupingPolicies(permission)
|
||||
visited[permissionId] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if name != role.Name {
|
||||
@ -113,11 +127,25 @@ func UpdateRole(id string, role *Role) bool {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
visited = map[string]struct{}{}
|
||||
newRoleID := role.GetId()
|
||||
permissions = GetPermissionsByRole(newRoleID)
|
||||
for _, permission := range permissions {
|
||||
addGroupingPolicies(permission)
|
||||
addPolicies(permission)
|
||||
visited[permission.GetId()] = struct{}{}
|
||||
}
|
||||
|
||||
ancestorRoles = GetAncestorRoles(newRoleID)
|
||||
for _, r := range ancestorRoles {
|
||||
permissions := GetPermissionsByRole(r.GetId())
|
||||
for _, permission := range permissions {
|
||||
permissionId := permission.GetId()
|
||||
if _, ok := visited[permissionId]; !ok {
|
||||
addGroupingPolicies(permission)
|
||||
visited[permissionId] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
@ -154,7 +182,7 @@ func (role *Role) GetId() string {
|
||||
|
||||
func GetRolesByUser(userId string) []*Role {
|
||||
roles := []*Role{}
|
||||
err := adapter.Engine.Where("users like ?", "%"+userId+"%").Find(&roles)
|
||||
err := adapter.Engine.Where("users like ?", "%"+userId+"\"%").Find(&roles)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -182,13 +210,12 @@ func roleChangeTrigger(oldName string, newName string) error {
|
||||
}
|
||||
for _, role := range roles {
|
||||
for j, u := range role.Roles {
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
role.Roles[j] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
role.Roles[j] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", role.Name).Update(role)
|
||||
_, err = session.Where("name=?", role.Name).And("owner=?", role.Owner).Update(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -202,13 +229,12 @@ func roleChangeTrigger(oldName string, newName string) error {
|
||||
for _, permission := range permissions {
|
||||
for j, u := range permission.Roles {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
permission.Roles[j] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
permission.Roles[j] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", permission.Name).Update(permission)
|
||||
_, err = session.Where("name=?", permission.Name).And("owner=?", permission.Owner).Update(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -224,3 +250,64 @@ func GetMaskedRoles(roles []*Role) []*Role {
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
func GetRolesByNamePrefix(owner string, prefix string) []*Role {
|
||||
roles := []*Role{}
|
||||
err := adapter.Engine.Where("owner=? and name like ?", owner, prefix+"%").Find(&roles)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
func GetAncestorRoles(roleId string) []*Role {
|
||||
var (
|
||||
result []*Role
|
||||
roleMap = make(map[string]*Role)
|
||||
visited = make(map[string]bool)
|
||||
)
|
||||
|
||||
owner, _ := util.GetOwnerAndNameFromIdNoCheck(roleId)
|
||||
|
||||
allRoles := GetRoles(owner)
|
||||
for _, r := range allRoles {
|
||||
roleMap[r.GetId()] = r
|
||||
}
|
||||
|
||||
// Second, find all the roles that contain father roles
|
||||
for _, r := range allRoles {
|
||||
isContain, ok := visited[r.GetId()]
|
||||
if isContain {
|
||||
result = append(result, r)
|
||||
} else if !ok {
|
||||
rId := r.GetId()
|
||||
visited[rId] = containsRole(r, roleId, roleMap, visited)
|
||||
if visited[rId] {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// containsRole is a helper function to check if a slice of roles contains a specific roleId
|
||||
func containsRole(role *Role, roleId string, roleMap map[string]*Role, visited map[string]bool) bool {
|
||||
if isContain, ok := visited[role.GetId()]; ok {
|
||||
return isContain
|
||||
}
|
||||
|
||||
for _, subRole := range role.Roles {
|
||||
if subRole == roleId {
|
||||
return true
|
||||
}
|
||||
|
||||
r, ok := roleMap[subRole]
|
||||
if ok && containsRole(r, roleId, roleMap, visited) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -86,19 +86,30 @@ func NewSamlResponse(user *User, host string, certificate string, destination st
|
||||
authnStatement.CreateElement("saml:AuthnContext").CreateElement("saml:AuthnContextClassRef").SetText("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")
|
||||
|
||||
attributes := assertion.CreateElement("saml:AttributeStatement")
|
||||
|
||||
email := attributes.CreateElement("saml:Attribute")
|
||||
email.CreateAttr("Name", "Email")
|
||||
email.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
email.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Email)
|
||||
|
||||
name := attributes.CreateElement("saml:Attribute")
|
||||
name.CreateAttr("Name", "Name")
|
||||
name.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
name.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Name)
|
||||
|
||||
displayName := attributes.CreateElement("saml:Attribute")
|
||||
displayName.CreateAttr("Name", "DisplayName")
|
||||
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
|
||||
|
||||
roles := attributes.CreateElement("saml:Attribute")
|
||||
roles.CreateAttr("Name", "Roles")
|
||||
roles.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
ExtendUserWithRolesAndPermissions(user)
|
||||
for _, role := range user.Roles {
|
||||
roles.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(role.Name)
|
||||
}
|
||||
|
||||
return samlResponse, nil
|
||||
}
|
||||
|
||||
|
@ -23,29 +23,32 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
saml2 "github.com/russellhaering/gosaml2"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
|
||||
func ParseSamlResponse(samlResponse string, providerType string) (string, error) {
|
||||
func ParseSamlResponse(samlResponse string, provider *Provider, host string) (string, error) {
|
||||
samlResponse, _ = url.QueryUnescape(samlResponse)
|
||||
sp, err := buildSp(&Provider{Type: providerType}, samlResponse)
|
||||
sp, err := buildSp(provider, samlResponse, host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||
|
||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return assertionInfo.NameID, err
|
||||
}
|
||||
|
||||
func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method string, err error) {
|
||||
func GenerateSamlRequest(id, relayState, host, lang string) (auth string, method string, err error) {
|
||||
provider := GetProvider(id)
|
||||
if provider.Category != "SAML" {
|
||||
return "", "", fmt.Errorf(i18n.Translate(lang, "saml_sp:provider %s's category is not SAML"), provider.Name)
|
||||
}
|
||||
sp, err := buildSp(provider, "")
|
||||
|
||||
sp, err := buildSp(provider, "", host)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@ -67,35 +70,22 @@ func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method stri
|
||||
return auth, method, nil
|
||||
}
|
||||
|
||||
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
func buildSp(provider *Provider, samlResponse string, host string) (*saml2.SAMLServiceProvider, error) {
|
||||
_, origin := getOriginFromHost(host)
|
||||
|
||||
certStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{},
|
||||
}
|
||||
|
||||
certEncodedData := ""
|
||||
if samlResponse != "" {
|
||||
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
||||
} else if provider.IdP != "" {
|
||||
certEncodedData = provider.IdP
|
||||
}
|
||||
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
|
||||
certStore, err := buildSpCertificateStore(provider, samlResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idpCert, err := x509.ParseCertificate(certData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certStore.Roots = append(certStore.Roots, idpCert)
|
||||
|
||||
sp := &saml2.SAMLServiceProvider{
|
||||
ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin),
|
||||
AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin),
|
||||
IDPCertificateStore: &certStore,
|
||||
SignAuthnRequests: false,
|
||||
IDPCertificateStore: &certStore,
|
||||
SPKeyStore: dsig.RandomKeyStoreForTest(),
|
||||
}
|
||||
|
||||
if provider.Endpoint != "" {
|
||||
sp.IdentityProviderSSOURL = provider.Endpoint
|
||||
sp.IdentityProviderIssuer = provider.IssuerUrl
|
||||
@ -104,10 +94,45 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
|
||||
sp.SignAuthnRequests = true
|
||||
sp.SPKeyStore = buildSpKeyStore()
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func parseSamlResponse(samlResponse string, providerType string) string {
|
||||
func buildSpKeyStore() dsig.X509KeyStore {
|
||||
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &dsig.TLSCertKeyStore{
|
||||
PrivateKey: keyPair.PrivateKey,
|
||||
Certificate: keyPair.Certificate,
|
||||
}
|
||||
}
|
||||
|
||||
func buildSpCertificateStore(provider *Provider, samlResponse string) (dsig.MemoryX509CertificateStore, error) {
|
||||
certEncodedData := ""
|
||||
if samlResponse != "" {
|
||||
certEncodedData = getCertificateFromSamlResponse(samlResponse, provider.Type)
|
||||
} else if provider.IdP != "" {
|
||||
certEncodedData = provider.IdP
|
||||
}
|
||||
|
||||
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
|
||||
if err != nil {
|
||||
return dsig.MemoryX509CertificateStore{}, err
|
||||
}
|
||||
idpCert, err := x509.ParseCertificate(certData)
|
||||
if err != nil {
|
||||
return dsig.MemoryX509CertificateStore{}, err
|
||||
}
|
||||
|
||||
certStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{idpCert},
|
||||
}
|
||||
return certStore, nil
|
||||
}
|
||||
|
||||
func getCertificateFromSamlResponse(samlResponse string, providerType string) string {
|
||||
de, err := base64.StdEncoding.DecodeString(samlResponse)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -122,14 +147,3 @@ func parseSamlResponse(samlResponse string, providerType string) string {
|
||||
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
|
||||
return res[1]
|
||||
}
|
||||
|
||||
func buildSpKeyStore() dsig.X509KeyStore {
|
||||
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &dsig.TLSCertKeyStore{
|
||||
PrivateKey: keyPair.PrivateKey,
|
||||
Certificate: keyPair.Certificate,
|
||||
}
|
||||
}
|
||||
|
@ -55,9 +55,9 @@ type Syncer struct {
|
||||
Adapter *Adapter `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
func GetSyncerCount(owner, field, value string) int {
|
||||
func GetSyncerCount(owner, organization, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Syncer{})
|
||||
count, err := session.Count(&Syncer{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -75,10 +75,20 @@ func GetSyncers(owner string) []*Syncer {
|
||||
return syncers
|
||||
}
|
||||
|
||||
func GetPaginationSyncers(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Syncer {
|
||||
func GetOrganizationSyncers(owner, organization string) []*Syncer {
|
||||
syncers := []*Syncer{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&syncers, &Syncer{Owner: owner, Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return syncers
|
||||
}
|
||||
|
||||
func GetPaginationSyncers(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Syncer {
|
||||
syncers := []*Syncer{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&syncers)
|
||||
err := session.Find(&syncers, &Syncer{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ type Token struct {
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
|
||||
Code string `xorm:"varchar(100)" json:"code"`
|
||||
Code string `xorm:"varchar(100) index" json:"code"`
|
||||
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||
ExpiresIn int `json:"expiresIn"`
|
||||
@ -91,9 +91,9 @@ type IntrospectionResponse struct {
|
||||
Jti string `json:"jti,omitempty"`
|
||||
}
|
||||
|
||||
func GetTokenCount(owner, field, value string) int {
|
||||
func GetTokenCount(owner, organization, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Token{})
|
||||
count, err := session.Count(&Token{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -101,9 +101,9 @@ func GetTokenCount(owner, field, value string) int {
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetTokens(owner string) []*Token {
|
||||
func GetTokens(owner string, organization string) []*Token {
|
||||
tokens := []*Token{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&tokens, &Token{Owner: owner})
|
||||
err := adapter.Engine.Desc("created_time").Find(&tokens, &Token{Owner: owner, Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -111,10 +111,10 @@ func GetTokens(owner string) []*Token {
|
||||
return tokens
|
||||
}
|
||||
|
||||
func GetPaginationTokens(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Token {
|
||||
func GetPaginationTokens(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Token {
|
||||
tokens := []*Token{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&tokens)
|
||||
err := session.Find(&tokens, &Token{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -362,7 +362,8 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
}
|
||||
|
||||
token.CodeIsUsed = true
|
||||
updateUsedByCode(token)
|
||||
go updateUsedByCode(token)
|
||||
|
||||
tokenWrapper := &TokenWrapper{
|
||||
AccessToken: token.AccessToken,
|
||||
IdToken: token.AccessToken,
|
||||
|
@ -224,13 +224,16 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
||||
nowTime := time.Now()
|
||||
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
||||
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||
if application.RefreshExpireInHours == 0 {
|
||||
refreshExpireTime = expireTime
|
||||
}
|
||||
|
||||
user = refineUser(user)
|
||||
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
|
||||
name := util.GenerateId()
|
||||
jti := fmt.Sprintf("%s/%s", application.Owner, name)
|
||||
jti := util.GetId(application.Owner, name)
|
||||
|
||||
claims := Claims{
|
||||
User: user,
|
||||
|
127
object/user.go
127
object/user.go
@ -15,10 +15,12 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/xorm-io/core"
|
||||
@ -39,6 +41,7 @@ type User struct {
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
||||
LastName string `xorm:"varchar(100)" json:"lastName"`
|
||||
@ -48,6 +51,7 @@ type User struct {
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
Address []string `json:"address"`
|
||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||
@ -57,7 +61,6 @@ type User struct {
|
||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Language string `xorm:"varchar(100)" json:"language"`
|
||||
Gender string `xorm:"varchar(100)" json:"gender"`
|
||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||
@ -155,6 +158,7 @@ type User struct {
|
||||
Custom string `xorm:"custom varchar(100)" json:"custom"`
|
||||
|
||||
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
|
||||
MultiFactorAuths []*MfaProps `json:"multiFactorAuths"`
|
||||
|
||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
@ -247,6 +251,16 @@ func GetUsers(owner string) []*User {
|
||||
return users
|
||||
}
|
||||
|
||||
func GetUsersByTag(owner string, tag string) []*User {
|
||||
users := []*User{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&users, &User{Owner: owner, Tag: tag})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func GetSortedUsers(owner string, sorter string, limit int) []*User {
|
||||
users := []*User{}
|
||||
err := adapter.Engine.Desc(sorter).Limit(limit, 0).Find(&users, &User{Owner: owner})
|
||||
@ -399,6 +413,12 @@ func GetMaskedUser(user *User) *User {
|
||||
manageAccount.Password = "***"
|
||||
}
|
||||
}
|
||||
|
||||
if user.MultiFactorAuths != nil {
|
||||
for i, props := range user.MultiFactorAuths {
|
||||
user.MultiFactorAuths[i] = GetMaskedProps(props)
|
||||
}
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
@ -423,7 +443,7 @@ func GetLastUser(owner string) *User {
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) bool {
|
||||
func UpdateUser(id string, user *User, columns []string, isAdmin bool) bool {
|
||||
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
oldUser := getUser(owner, name)
|
||||
if oldUser == nil {
|
||||
@ -449,12 +469,19 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
||||
if len(columns) == 0 {
|
||||
columns = []string{
|
||||
"owner", "display_name", "avatar",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
||||
"signin_wrong_times", "last_signin_wrong_time",
|
||||
"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",
|
||||
"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",
|
||||
"spotify", "strava", "stripe", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
|
||||
"yammer", "yandex", "zoom", "custom",
|
||||
}
|
||||
}
|
||||
if isGlobalAdmin {
|
||||
if isAdmin {
|
||||
columns = append(columns, "name", "email", "phone", "country_code")
|
||||
}
|
||||
|
||||
@ -513,7 +540,10 @@ func AddUser(user *User) bool {
|
||||
user.UpdateUserHash()
|
||||
user.PreHash = user.Hash
|
||||
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||
updated := user.refreshAvatar()
|
||||
if updated && user.PermanentAvatar != "*" {
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||
}
|
||||
|
||||
user.Ranking = GetUserCount(user.Owner, "", "") + 1
|
||||
|
||||
@ -652,13 +682,12 @@ func userChangeTrigger(oldName string, newName string) error {
|
||||
for _, role := range roles {
|
||||
for j, u := range role.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
role.Users[j] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
role.Users[j] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", role.Name).Update(role)
|
||||
_, err = session.Where("name=?", role.Name).And("owner=?", role.Owner).Update(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -672,13 +701,12 @@ func userChangeTrigger(oldName string, newName string) error {
|
||||
for _, permission := range permissions {
|
||||
for j, u := range permission.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
permission.Users[j] = split[0] + "/" + split[1]
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
permission.Users[j] = util.GetId(owner, newName)
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", permission.Name).Update(permission)
|
||||
_, err = session.Where("name=?", permission.Name).And("owner=?", permission.Owner).Update(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -693,3 +721,72 @@ func userChangeTrigger(oldName string, newName string) error {
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
||||
func (user *User) refreshAvatar() bool {
|
||||
var err error
|
||||
var fileBuffer *bytes.Buffer
|
||||
var ext string
|
||||
|
||||
// Gravatar + Identicon
|
||||
if strings.Contains(user.Avatar, "Gravatar") && user.Email != "" {
|
||||
client := proxy.ProxyHttpClient
|
||||
has, err := hasGravatar(client, user.Email)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
fileBuffer, ext, err = getGravatarFileBuffer(client, user.Email)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fileBuffer == nil && strings.Contains(user.Avatar, "Identicon") {
|
||||
fileBuffer, ext, err = getIdenticonFileBuffer(user.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if fileBuffer != nil {
|
||||
avatarUrl := getPermanentAvatarUrlFromBuffer(user.Owner, user.Name, fileBuffer, ext, true)
|
||||
user.Avatar = avatarUrl
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (user *User) IsMfaEnabled() bool {
|
||||
return len(user.MultiFactorAuths) > 0
|
||||
}
|
||||
|
||||
func (user *User) GetPreferMfa(masked bool) *MfaProps {
|
||||
if len(user.MultiFactorAuths) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if masked {
|
||||
if len(user.MultiFactorAuths) == 1 {
|
||||
return GetMaskedProps(user.MultiFactorAuths[0])
|
||||
}
|
||||
for _, v := range user.MultiFactorAuths {
|
||||
if v.IsPreferred {
|
||||
return GetMaskedProps(v)
|
||||
}
|
||||
}
|
||||
return GetMaskedProps(user.MultiFactorAuths[0])
|
||||
} else {
|
||||
if len(user.MultiFactorAuths) == 1 {
|
||||
return user.MultiFactorAuths[0]
|
||||
}
|
||||
for _, v := range user.MultiFactorAuths {
|
||||
if v.IsPreferred {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return user.MultiFactorAuths[0]
|
||||
}
|
||||
}
|
||||
|
@ -35,5 +35,6 @@ func (user *User) UpdateUserPassword(organization *Organization) {
|
||||
if credManager != nil {
|
||||
hashedPassword := credManager.GetHashedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
|
||||
user.Password = hashedPassword
|
||||
user.PasswordType = organization.PasswordType
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -76,13 +77,17 @@ func GetUserByFields(organization string, field string) *User {
|
||||
}
|
||||
|
||||
func SetUserField(user *User, field string, value string) bool {
|
||||
bean := make(map[string]interface{})
|
||||
if field == "password" {
|
||||
organization := GetOrganizationByUser(user)
|
||||
user.UpdateUserPassword(organization)
|
||||
value = user.Password
|
||||
bean[strings.ToLower(field)] = user.Password
|
||||
bean["password_type"] = user.PasswordType
|
||||
} else {
|
||||
bean[strings.ToLower(field)] = value
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Table(user).ID(core.PK{user.Owner, user.Name}).Update(map[string]interface{}{strings.ToLower(field): value})
|
||||
affected, err := adapter.Engine.Table(user).ID(core.PK{user.Owner, user.Name}).Update(bean)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -131,6 +136,12 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
|
||||
if user.DisplayName == "" {
|
||||
user.DisplayName = userInfo.DisplayName
|
||||
}
|
||||
} else if user.DisplayName == "" {
|
||||
if userInfo.Username != "" {
|
||||
user.DisplayName = userInfo.Username
|
||||
} else {
|
||||
user.DisplayName = userInfo.Id
|
||||
}
|
||||
}
|
||||
if userInfo.Email != "" {
|
||||
propertyName := fmt.Sprintf("oauth_%s_email", providerType)
|
||||
@ -173,6 +184,123 @@ func ClearUserOAuthProperties(user *User, providerType string) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang string) (bool, string) {
|
||||
organization := GetOrganizationByUser(oldUser)
|
||||
var itemsChanged []*AccountItem
|
||||
|
||||
if oldUser.Owner != newUser.Owner {
|
||||
item := GetAccountItemByName("Organization", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Name != newUser.Name {
|
||||
item := GetAccountItemByName("Name", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Id != newUser.Id {
|
||||
item := GetAccountItemByName("ID", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.DisplayName != newUser.DisplayName {
|
||||
item := GetAccountItemByName("Display name", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Avatar != newUser.Avatar {
|
||||
item := GetAccountItemByName("Avatar", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Type != newUser.Type {
|
||||
item := GetAccountItemByName("User type", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
// The password is *** when not modified
|
||||
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
||||
item := GetAccountItemByName("Password", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Email != newUser.Email {
|
||||
item := GetAccountItemByName("Email", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Phone != newUser.Phone {
|
||||
item := GetAccountItemByName("Phone", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.CountryCode != newUser.CountryCode {
|
||||
item := GetAccountItemByName("Country code", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Region != newUser.Region {
|
||||
item := GetAccountItemByName("Country/Region", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Location != newUser.Location {
|
||||
item := GetAccountItemByName("Location", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Affiliation != newUser.Affiliation {
|
||||
item := GetAccountItemByName("Affiliation", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Title != newUser.Title {
|
||||
item := GetAccountItemByName("Title", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Homepage != newUser.Homepage {
|
||||
item := GetAccountItemByName("Homepage", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Bio != newUser.Bio {
|
||||
item := GetAccountItemByName("Bio", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.Tag != newUser.Tag {
|
||||
item := GetAccountItemByName("Tag", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
||||
item := GetAccountItemByName("Signup application", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
||||
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
||||
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
||||
item := GetAccountItemByName("Properties", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
oldUserTwoFactorAuthJson, _ := json.Marshal(oldUser.MultiFactorAuths)
|
||||
newUserTwoFactorAuthJson, _ := json.Marshal(newUser.MultiFactorAuths)
|
||||
if string(oldUserTwoFactorAuthJson) != string(newUserTwoFactorAuthJson) {
|
||||
item := GetAccountItemByName("Multi-factor authentication", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||
item := GetAccountItemByName("Is admin", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin {
|
||||
item := GetAccountItemByName("Is global admin", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
||||
item := GetAccountItemByName("Is forbidden", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
||||
item := GetAccountItemByName("Is deleted", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
for i := range itemsChanged {
|
||||
if pass, err := CheckAccountItemModifyRule(itemsChanged[i], isAdmin, lang); !pass {
|
||||
return pass, err
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (user *User) GetCountryCode(countryCode string) string {
|
||||
if countryCode != "" {
|
||||
return countryCode
|
||||
@ -187,3 +315,11 @@ func (user *User) GetCountryCode(countryCode string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (user *User) IsAdminUser() bool {
|
||||
if user == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return user.IsAdmin || user.IsGlobalAdmin
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@ -38,6 +39,11 @@ const (
|
||||
timeoutError = 3
|
||||
)
|
||||
|
||||
const (
|
||||
VerifyTypePhone = "phone"
|
||||
VerifyTypeEmail = "email"
|
||||
)
|
||||
|
||||
type VerificationRecord struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
@ -213,6 +219,14 @@ func CheckSigninCode(user *User, dest, code, lang string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func GetVerifyType(username string) (verificationCodeType string) {
|
||||
if strings.Contains(username, "@") {
|
||||
return VerifyTypeEmail
|
||||
} else {
|
||||
return VerifyTypeEmail
|
||||
}
|
||||
}
|
||||
|
||||
// From Casnode/object/validateCode.go line 116
|
||||
var stdNums = []byte("0123456789")
|
||||
|
||||
|
@ -42,9 +42,9 @@ type Webhook struct {
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
}
|
||||
|
||||
func GetWebhookCount(owner, field, value string) int {
|
||||
func GetWebhookCount(owner, organization, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Webhook{})
|
||||
count, err := session.Count(&Webhook{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -52,9 +52,9 @@ func GetWebhookCount(owner, field, value string) int {
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetWebhooks(owner string) []*Webhook {
|
||||
func GetWebhooks(owner string, organization string) []*Webhook {
|
||||
webhooks := []*Webhook{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&webhooks, &Webhook{Owner: owner})
|
||||
err := adapter.Engine.Desc("created_time").Find(&webhooks, &Webhook{Owner: owner, Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -62,10 +62,10 @@ func GetWebhooks(owner string) []*Webhook {
|
||||
return webhooks
|
||||
}
|
||||
|
||||
func GetPaginationWebhooks(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Webhook {
|
||||
func GetPaginationWebhooks(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Webhook {
|
||||
webhooks := []*Webhook{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&webhooks)
|
||||
err := session.Find(&webhooks, &Webhook{Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -28,21 +28,21 @@ type AlipayPaymentProvider struct {
|
||||
Client *alipay.Client
|
||||
}
|
||||
|
||||
func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider {
|
||||
func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) (*AlipayPaymentProvider, error) {
|
||||
pp := &AlipayPaymentProvider{}
|
||||
|
||||
client, err := alipay.NewClient(appId, appPrivateKey, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = client.SetCertSnByContent([]byte(appCertificate), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pp.Client = client
|
||||
return pp
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
|
@ -22,11 +22,23 @@ type PaymentProvider interface {
|
||||
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||
}
|
||||
|
||||
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
|
||||
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) {
|
||||
if typ == "Alipay" {
|
||||
return NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
||||
newAlipayPaymentProvider, err := NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newAlipayPaymentProvider, nil
|
||||
} else if typ == "GC" {
|
||||
return NewGcPaymentProvider(appId, clientSecret, host)
|
||||
return NewGcPaymentProvider(appId, clientSecret, host), nil
|
||||
} else if typ == "WeChat Pay" {
|
||||
// appId, mchId, mchCertSerialNumber, apiV3Key, privateKey
|
||||
newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, appId, appCertificate, clientSecret, appPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newWechatPaymentProvider, nil
|
||||
}
|
||||
return nil
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user