mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-20 02:43:49 +08:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
117ee509cf | |||
daf3d374b5 |
91
.github/workflows/build.yml
vendored
91
.github/workflows/build.yml
vendored
@ -9,18 +9,19 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:5.7
|
image: mysql:5.7
|
||||||
env:
|
env:
|
||||||
MYSQL_DATABASE: casdoor
|
MYSQL_DATABASE: casdoor
|
||||||
MYSQL_ROOT_PASSWORD: 123456
|
MYSQL_ROOT_PASSWORD: 123456
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.5'
|
go-version: '^1.16.5'
|
||||||
|
cache-dependency-path: ./go.mod
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: |
|
run: |
|
||||||
go test -v $(go list ./...) -tags skipCi
|
go test -v $(go list ./...) -tags skipCi
|
||||||
@ -31,14 +32,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ go-tests ]
|
needs: [ go-tests ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
# cache
|
cache: 'yarn'
|
||||||
- uses: c-hive/gha-yarn-cache@v2
|
cache-dependency-path: ./web/yarn.lock
|
||||||
with:
|
|
||||||
directory: ./web
|
|
||||||
- run: yarn install && CI=false yarn run build
|
- run: yarn install && CI=false yarn run build
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
|
|
||||||
@ -47,10 +46,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ go-tests ]
|
needs: [ go-tests ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.5'
|
go-version: '^1.16.5'
|
||||||
|
cache-dependency-path: ./go.mod
|
||||||
- run: go version
|
- run: go version
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
@ -63,13 +63,14 @@ jobs:
|
|||||||
needs: [ go-tests ]
|
needs: [ go-tests ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.5'
|
go-version: '^1.16.5'
|
||||||
|
cache-dependency-path: ./go.mod
|
||||||
|
|
||||||
# gen a dummy config file
|
# gen a dummy config file
|
||||||
- run: touch dummy.yml
|
- run: touch dummy.yml
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
@ -82,35 +83,35 @@ jobs:
|
|||||||
needs: [ go-tests ]
|
needs: [ go-tests ]
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:5.7
|
image: mysql:5.7
|
||||||
env:
|
env:
|
||||||
MYSQL_DATABASE: casdoor
|
MYSQL_DATABASE: casdoor
|
||||||
MYSQL_ROOT_PASSWORD: 123456
|
MYSQL_ROOT_PASSWORD: 123456
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.5'
|
go-version: '^1.16.5'
|
||||||
- uses: actions/setup-node@v2
|
cache-dependency-path: ./go.mod
|
||||||
with:
|
- name: start backend
|
||||||
node-version: 16
|
|
||||||
- name: back start
|
|
||||||
run: nohup go run ./main.go &
|
run: nohup go run ./main.go &
|
||||||
working-directory: ./
|
working-directory: ./
|
||||||
- name: front install
|
- uses: actions/setup-node@v3
|
||||||
run: yarn install
|
|
||||||
working-directory: ./web
|
|
||||||
- name: front start
|
|
||||||
run: nohup yarn start &
|
|
||||||
working-directory: ./web
|
|
||||||
- uses: cypress-io/github-action@v4
|
|
||||||
with:
|
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: 'http://localhost:7001'
|
||||||
wait-on-timeout: 180
|
wait-on-timeout: 180
|
||||||
|
working-directory: ./web
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
if: failure()
|
if: failure()
|
||||||
@ -121,7 +122,7 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: cypress-videos
|
name: cypress-videos
|
||||||
path: ./web/cypress/videos
|
path: ./web/cypress/videos
|
||||||
|
|
||||||
release-and-push:
|
release-and-push:
|
||||||
name: Release And Push
|
name: Release And Push
|
||||||
@ -130,11 +131,11 @@ jobs:
|
|||||||
needs: [ frontend, backend, linter, e2e ]
|
needs: [ frontend, backend, linter, e2e ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: -1
|
fetch-depth: -1
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
|
|
||||||
@ -166,10 +167,10 @@ jobs:
|
|||||||
elif [ ${old_array[1]} != ${new_array[1]} ]
|
elif [ ${old_array[1]} != ${new_array[1]} ]
|
||||||
then
|
then
|
||||||
echo ::set-output name=push::'true'
|
echo ::set-output name=push::'true'
|
||||||
|
|
||||||
else
|
else
|
||||||
echo ::set-output name=push::'false'
|
echo ::set-output name=push::'false'
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
|
@ -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/conf/app.conf ./conf/app.conf
|
||||||
COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
||||||
COPY --from=FRONT /web/build ./web/build
|
COPY --from=FRONT /web/build ./web/build
|
||||||
|
RUN mkdir tempFiles
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/bash"]
|
ENTRYPOINT ["/bin/bash"]
|
||||||
CMD ["/docker-entrypoint.sh"]
|
CMD ["/docker-entrypoint.sh"]
|
||||||
|
@ -108,6 +108,7 @@ p, *, *, POST, /api/set-password, *, *
|
|||||||
p, *, *, POST, /api/send-verification-code, *, *
|
p, *, *, POST, /api/send-verification-code, *, *
|
||||||
p, *, *, GET, /api/get-captcha, *, *
|
p, *, *, GET, /api/get-captcha, *, *
|
||||||
p, *, *, POST, /api/verify-captcha, *, *
|
p, *, *, POST, /api/verify-captcha, *, *
|
||||||
|
p, *, *, POST, /api/verify-code, *, *
|
||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
|
@ -137,7 +137,7 @@ func (c *ApiController) Signup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var checkPhone string
|
var checkPhone string
|
||||||
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
|
if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && form.Phone != "" {
|
||||||
checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode)
|
checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode)
|
||||||
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
|
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
|
||||||
if checkResult.Code != object.VerificationSuccess {
|
if checkResult.Code != object.VerificationSuccess {
|
||||||
|
@ -246,33 +246,14 @@ func (c *ApiController) Login() {
|
|||||||
var msg string
|
var msg string
|
||||||
|
|
||||||
if form.Password == "" {
|
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 {
|
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)))
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if verificationCodeType == "phone" {
|
|
||||||
|
verificationCodeType := object.GetVerifyType(form.Username)
|
||||||
|
var checkDest string
|
||||||
|
if verificationCodeType == object.VerifyTypePhone {
|
||||||
form.CountryCode = user.GetCountryCode(form.CountryCode)
|
form.CountryCode = user.GetCountryCode(form.CountryCode)
|
||||||
var ok bool
|
var ok bool
|
||||||
if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok {
|
if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok {
|
||||||
@ -281,7 +262,8 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkResult = object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
|
// check result through Email or Phone
|
||||||
|
checkResult := object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
|
||||||
if len(checkResult) != 0 {
|
if len(checkResult) != 0 {
|
||||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
||||||
return
|
return
|
||||||
@ -357,7 +339,7 @@ func (c *ApiController) Login() {
|
|||||||
userInfo := &idp.UserInfo{}
|
userInfo := &idp.UserInfo{}
|
||||||
if provider.Category == "SAML" {
|
if provider.Category == "SAML" {
|
||||||
// SAML
|
// SAML
|
||||||
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider.Type)
|
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider, c.Ctx.Request.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -564,7 +546,7 @@ func (c *ApiController) Login() {
|
|||||||
func (c *ApiController) GetSamlLogin() {
|
func (c *ApiController) GetSamlLogin() {
|
||||||
providerId := c.Input().Get("id")
|
providerId := c.Input().Get("id")
|
||||||
relayState := c.Input().Get("relayState")
|
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 {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,11 @@ func (c *RootController) CasProxyValidate() {
|
|||||||
c.CasP3ServiceAndProxyValidate()
|
c.CasP3ServiceAndProxyValidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func queryUnescape(service string) string {
|
||||||
|
s, _ := url.QueryUnescape(service)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RootController) CasP3ServiceAndProxyValidate() {
|
func (c *RootController) CasP3ServiceAndProxyValidate() {
|
||||||
ticket := c.Input().Get("ticket")
|
ticket := c.Input().Get("ticket")
|
||||||
format := c.Input().Get("format")
|
format := c.Input().Get("format")
|
||||||
@ -91,7 +96,7 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
|
|||||||
// find the token
|
// find the token
|
||||||
if ok {
|
if ok {
|
||||||
// check whether service is the one for which we previously issued token
|
// 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
|
serviceResponse.Success = response
|
||||||
} else {
|
} else {
|
||||||
// service not match
|
// service not match
|
||||||
|
123
controllers/chat.go
Normal file
123
controllers/chat.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// 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")
|
||||||
|
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()
|
||||||
|
}
|
@ -65,7 +65,7 @@ func (c *ApiController) GetLdapUsers() {
|
|||||||
// })
|
// })
|
||||||
//}
|
//}
|
||||||
|
|
||||||
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
|
users, err := conn.GetLdapUsers(ldapServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -80,15 +80,16 @@ func (c *ApiController) GetLdapUsers() {
|
|||||||
Cn: user.Cn,
|
Cn: user.Cn,
|
||||||
GroupId: user.GidNumber,
|
GroupId: user.GidNumber,
|
||||||
// GroupName: groupsMap[user.GidNumber].Cn,
|
// GroupName: groupsMap[user.GidNumber].Cn,
|
||||||
Uuid: user.Uuid,
|
Uuid: user.Uuid,
|
||||||
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
DisplayName: user.DisplayName,
|
||||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
||||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
||||||
|
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
||||||
})
|
})
|
||||||
uuids = append(uuids, user.Uuid)
|
uuids = append(uuids, user.Uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
existUuids := object.CheckLdapUuidExist(ldapServer.Owner, uuids)
|
existUuids := object.GetExistUuids(ldapServer.Owner, uuids)
|
||||||
|
|
||||||
c.ResponseOk(resp, existUuids)
|
c.ResponseOk(resp, existUuids)
|
||||||
}
|
}
|
||||||
@ -131,7 +132,7 @@ func (c *ApiController) AddLdap() {
|
|||||||
return
|
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"))
|
c.ResponseError(c.T("general:Missing parameter"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -160,7 +161,7 @@ func (c *ApiController) AddLdap() {
|
|||||||
func (c *ApiController) UpdateLdap() {
|
func (c *ApiController) UpdateLdap() {
|
||||||
var ldap object.Ldap
|
var ldap object.Ldap
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &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"))
|
c.ResponseError(c.T("general:Missing parameter"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
132
controllers/message.go
Normal file
132
controllers/message.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
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, field, value)))
|
||||||
|
messages := object.GetMaskedMessages(object.GetPaginationMessages(owner, 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddMessage(&message))
|
||||||
|
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()
|
||||||
|
}
|
@ -21,9 +21,8 @@ import (
|
|||||||
// GetSystemInfo
|
// GetSystemInfo
|
||||||
// @Title GetSystemInfo
|
// @Title GetSystemInfo
|
||||||
// @Tag System API
|
// @Tag System API
|
||||||
// @Description get user's system info
|
// @Description get system info like CPU and memory usage
|
||||||
// @Param id query string true "The id ( owner/name ) of the user"
|
// @Success 200 {object} util.SystemInfo The Response object
|
||||||
// @Success 200 {object} object.SystemInfo The Response object
|
|
||||||
// @router /get-system-info [get]
|
// @router /get-system-info [get]
|
||||||
func (c *ApiController) GetSystemInfo() {
|
func (c *ApiController) GetSystemInfo() {
|
||||||
_, ok := c.RequireAdmin()
|
_, ok := c.RequireAdmin()
|
||||||
@ -43,8 +42,8 @@ func (c *ApiController) GetSystemInfo() {
|
|||||||
// GetVersionInfo
|
// GetVersionInfo
|
||||||
// @Title GetVersionInfo
|
// @Title GetVersionInfo
|
||||||
// @Tag System API
|
// @Tag System API
|
||||||
// @Description get local git repo's latest release version info
|
// @Description get version info like Casdoor release version and commit ID
|
||||||
// @Success 200 {string} local latest version hash of Casdoor
|
// @Success 200 {object} util.VersionInfo The Response object
|
||||||
// @router /get-version-info [get]
|
// @router /get-version-info [get]
|
||||||
func (c *ApiController) GetVersionInfo() {
|
func (c *ApiController) GetVersionInfo() {
|
||||||
versionInfo, err := util.GetVersionInfo()
|
versionInfo, err := util.GetVersionInfo()
|
||||||
|
@ -128,7 +128,7 @@ func (c *ApiController) DeleteToken() {
|
|||||||
// @Title GetOAuthCode
|
// @Title GetOAuthCode
|
||||||
// @Tag Token API
|
// @Tag Token API
|
||||||
// @Description get OAuth code
|
// @Description get OAuth code
|
||||||
// @Param id query string true "The id ( owner/name ) of user"
|
// @Param user_id query string true "The id ( owner/name ) of user"
|
||||||
// @Param client_id query string true "OAuth client id"
|
// @Param client_id query string true "OAuth client id"
|
||||||
// @Param response_type query string true "OAuth response type"
|
// @Param response_type query string true "OAuth response type"
|
||||||
// @Param redirect_uri query string true "OAuth redirect URI"
|
// @Param redirect_uri query string true "OAuth redirect URI"
|
||||||
|
@ -84,6 +84,7 @@ func (c *ApiController) GetUsers() {
|
|||||||
// @Param owner query string false "The owner of the user"
|
// @Param owner query string false "The owner of the user"
|
||||||
// @Param email query string false "The email of the user"
|
// @Param email query string false "The email of the user"
|
||||||
// @Param phone query string false "The phone 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
|
// @Success 200 {object} object.User The Response object
|
||||||
// @router /get-user [get]
|
// @router /get-user [get]
|
||||||
func (c *ApiController) GetUser() {
|
func (c *ApiController) GetUser() {
|
||||||
@ -94,13 +95,13 @@ func (c *ApiController) GetUser() {
|
|||||||
|
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
if owner == "" {
|
if owner == "" {
|
||||||
owner, _ = util.GetOwnerAndNameFromId(id)
|
owner = util.GetOwnerFromId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
|
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
|
||||||
if !organization.IsProfilePublic {
|
if !organization.IsProfilePublic {
|
||||||
requestUserId := c.GetSessionUsername()
|
requestUserId := c.GetSessionUsername()
|
||||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, owner, false, c.GetAcceptLanguage())
|
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
||||||
if !hasPermission {
|
if !hasPermission {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -137,10 +138,6 @@ func (c *ApiController) UpdateUser() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
columnsStr := c.Input().Get("columns")
|
columnsStr := c.Input().Get("columns")
|
||||||
|
|
||||||
if id == "" {
|
|
||||||
id = c.GetSessionUsername()
|
|
||||||
}
|
|
||||||
|
|
||||||
var user object.User
|
var user object.User
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -148,10 +145,27 @@ func (c *ApiController) UpdateUser() {
|
|||||||
return
|
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 msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if pass, err := checkPermissionForUpdateUser(oldUser, &user, c); !pass {
|
||||||
|
c.ResponseError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
columns := []string{}
|
columns := []string{}
|
||||||
if columnsStr != "" {
|
if columnsStr != "" {
|
||||||
@ -160,11 +174,6 @@ func (c *ApiController) UpdateUser() {
|
|||||||
|
|
||||||
isGlobalAdmin := c.IsGlobalAdmin()
|
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, isGlobalAdmin)
|
||||||
if affected {
|
if affected {
|
||||||
object.UpdateUserToOriginalDatabase(&user)
|
object.UpdateUserToOriginalDatabase(&user)
|
||||||
@ -275,14 +284,34 @@ func (c *ApiController) SetPassword() {
|
|||||||
userName := c.Ctx.Request.Form.Get("userName")
|
userName := c.Ctx.Request.Form.Get("userName")
|
||||||
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
||||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||||
|
code := c.Ctx.Request.Form.Get("code")
|
||||||
|
|
||||||
|
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)
|
userId := util.GetId(userOwner, userName)
|
||||||
|
|
||||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, userOwner, true, c.GetAcceptLanguage())
|
requestUserId := c.GetSessionUsername()
|
||||||
if !hasPermission {
|
if requestUserId == "" && code == "" {
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
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)
|
targetUser := object.GetUser(userId)
|
||||||
@ -295,16 +324,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
|
targetUser.Password = newPassword
|
||||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
|
@ -20,8 +20,7 @@ import (
|
|||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkPermissionForUpdateUser(userId string, newUser object.User, c *ApiController) (bool, string) {
|
func checkPermissionForUpdateUser(oldUser, newUser *object.User, c *ApiController) (bool, string) {
|
||||||
oldUser := object.GetUser(userId)
|
|
||||||
organization := object.GetOrganizationByUser(oldUser)
|
organization := object.GetOrganizationByUser(oldUser)
|
||||||
var itemsChanged []*object.AccountItem
|
var itemsChanged []*object.AccountItem
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@ -110,7 +111,7 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
sendResp := errors.New("invalid dest type")
|
sendResp := errors.New("invalid dest type")
|
||||||
|
|
||||||
switch destType {
|
switch destType {
|
||||||
case "email":
|
case object.VerifyTypeEmail:
|
||||||
if !util.IsEmailValid(dest) {
|
if !util.IsEmailValid(dest) {
|
||||||
c.ResponseError(c.T("check:Email is invalid"))
|
c.ResponseError(c.T("check:Email is invalid"))
|
||||||
return
|
return
|
||||||
@ -132,7 +133,7 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
|
|
||||||
provider := application.GetEmailProvider()
|
provider := application.GetEmailProvider()
|
||||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||||
case "phone":
|
case object.VerifyTypePhone:
|
||||||
if method == LoginVerification || method == ForgetVerification {
|
if method == LoginVerification || method == ForgetVerification {
|
||||||
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||||
dest = user.Phone
|
dest = user.Phone
|
||||||
@ -187,7 +188,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
|
|
||||||
checkDest := dest
|
checkDest := dest
|
||||||
organization := object.GetOrganizationByUser(user)
|
organization := object.GetOrganizationByUser(user)
|
||||||
if destType == "phone" {
|
if destType == object.VerifyTypePhone {
|
||||||
if object.HasUserByField(user.Owner, "phone", dest) {
|
if object.HasUserByField(user.Owner, "phone", dest) {
|
||||||
c.ResponseError(c.T("check:Phone already exists"))
|
c.ResponseError(c.T("check:Phone already exists"))
|
||||||
return
|
return
|
||||||
@ -207,7 +208,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
|
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if destType == "email" {
|
} else if destType == object.VerifyTypeEmail {
|
||||||
if object.HasUserByField(user.Owner, "email", dest) {
|
if object.HasUserByField(user.Owner, "email", dest) {
|
||||||
c.ResponseError(c.T("check:Email already exists"))
|
c.ResponseError(c.T("check:Email already exists"))
|
||||||
return
|
return
|
||||||
@ -230,10 +231,10 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch destType {
|
switch destType {
|
||||||
case "email":
|
case object.VerifyTypeEmail:
|
||||||
user.Email = dest
|
user.Email = dest
|
||||||
object.SetUserField(user, "email", user.Email)
|
object.SetUserField(user, "email", user.Email)
|
||||||
case "phone":
|
case object.VerifyTypePhone:
|
||||||
user.Phone = dest
|
user.Phone = dest
|
||||||
object.SetUserField(user, "phone", user.Phone)
|
object.SetUserField(user, "phone", user.Phone)
|
||||||
default:
|
default:
|
||||||
@ -245,6 +246,60 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyCode
|
||||||
|
// @Tag Account API
|
||||||
|
// @Title VerifyCode
|
||||||
|
// @router /api/verify-code [post]
|
||||||
|
func (c *ApiController) VerifyCode() {
|
||||||
|
var form RequestForm
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *object.User
|
||||||
|
if form.Name != "" {
|
||||||
|
user = object.GetUserByFields(form.Organization, form.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkDest string
|
||||||
|
if strings.Contains(form.Username, "@") {
|
||||||
|
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
|
||||||
|
form.Username = user.Email
|
||||||
|
}
|
||||||
|
checkDest = form.Username
|
||||||
|
} else {
|
||||||
|
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)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
verificationCodeType := object.GetVerifyType(form.Username)
|
||||||
|
if verificationCodeType == object.VerifyTypePhone {
|
||||||
|
form.CountryCode = user.GetCountryCode(form.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))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := object.CheckVerificationCode(checkDest, form.Code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess {
|
||||||
|
c.ResponseError(result.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
object.DisableVerificationCode(checkDest)
|
||||||
|
c.SetSession("verifiedCode", form.Code)
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyCaptcha ...
|
// VerifyCaptcha ...
|
||||||
// @Title VerifyCaptcha
|
// @Title VerifyCaptcha
|
||||||
// @Tag Verification API
|
// @Tag Verification API
|
||||||
|
4
go.mod
4
go.mod
@ -17,14 +17,16 @@ require (
|
|||||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/denisenkom/go-mssqldb v0.9.0
|
github.com/denisenkom/go-mssqldb v0.9.0
|
||||||
|
github.com/fogleman/gg v1.3.0
|
||||||
github.com/forestmgy/ldapserver v1.1.0
|
github.com/forestmgy/ldapserver v1.1.0
|
||||||
github.com/go-git/go-git/v5 v5.6.0
|
github.com/go-git/go-git/v5 v5.6.0
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0
|
github.com/go-ldap/ldap/v3 v3.3.0
|
||||||
github.com/go-mysql-org/go-mysql v1.7.0
|
github.com/go-mysql-org/go-mysql v1.7.0
|
||||||
github.com/go-pay/gopay v1.5.72
|
github.com/go-pay/gopay v1.5.72
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
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-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/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
|
19
go.sum
19
go.sum
@ -173,6 +173,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.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/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/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 h1:gvil4nuLhqPEL8SugCkFhRyA0/lIvRdwZSqlrw63ll4=
|
||||||
github.com/forestmgy/ldapserver v1.1.0/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM=
|
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=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
@ -222,10 +224,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 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
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-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.6 h1:3tv+itza9WpX5tryRQx4GwxCCBrCIiJ8GIkOhxiAmmU=
|
||||||
github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w=
|
github.com/go-webauthn/revoke v0.1.6/go.mod h1:TB4wuW4tPlwgF3znujA96F70/YSQXHPPWl7vgY09Iy8=
|
||||||
github.com/go-webauthn/webauthn v0.8.2 h1:8KLIbpldjz9KVGHfqEgJNbkhd7bbRXhNw4QWFJE15oA=
|
github.com/go-webauthn/webauthn v0.6.0 h1:uLInMApSvBfP+vEFasNE0rnVPG++fjp7lmAIvNhe+UU=
|
||||||
github.com/go-webauthn/webauthn v0.8.2/go.mod h1:d+ezx/jMCNDiqSMzOchuynKb9CVU1NM9BumOnokfcVQ=
|
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 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
|
||||||
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
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=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
@ -234,10 +236,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 h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
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.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 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
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 h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
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/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-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@ -658,8 +663,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-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-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-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.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.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 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
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=
|
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@ -674,6 +681,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-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/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-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/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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
@ -741,6 +749,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.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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
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 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
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=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@ -833,6 +842,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.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
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 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
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=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -843,6 +853,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.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.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.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 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
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=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
"Email is invalid": "E-Mail ist ungültig",
|
"Email is invalid": "E-Mail ist ungültig",
|
||||||
"Empty username.": "Leerer Benutzername.",
|
"Empty username.": "Leerer Benutzername.",
|
||||||
"FirstName cannot be blank": "Vorname darf nicht leer sein",
|
"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",
|
"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",
|
"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",
|
"Organization does not exist": "Organisation existiert nicht",
|
||||||
"Password must have at least 6 characters": "Das Passwort muss mindestens 6 Zeichen enthalten",
|
"Password must have at least 6 characters": "Das Passwort muss mindestens 6 Zeichen enthalten",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"Phone number is invalid": "Die Telefonnummer ist ungültig",
|
"Phone number is invalid": "Die Telefonnummer ist ungültig",
|
||||||
"Session outdated, please login again": "Sitzung abgelaufen, bitte erneut anmelden",
|
"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 is forbidden to sign in, please contact the administrator": "Dem Benutzer ist der Zugang verboten, bitte kontaktieren Sie den Administrator",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Der Benutzername darf nur alphanumerische Zeichen, Unterstriche oder Bindestriche enthalten, keine aufeinanderfolgenden Bindestriche oder Unterstriche haben und darf nicht mit einem Bindestrich oder Unterstrich beginnen oder enden.",
|
"The 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 already exists": "Benutzername existiert bereits",
|
||||||
"Username cannot be an email address": "Benutzername kann keine E-Mail-Adresse sein",
|
"Username cannot be an email address": "Benutzername kann keine E-Mail-Adresse sein",
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
"Email is invalid": "Email is invalid",
|
"Email is invalid": "Email is invalid",
|
||||||
"Empty username.": "Empty username.",
|
"Empty username.": "Empty username.",
|
||||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
"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",
|
"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",
|
"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",
|
"Organization does not exist": "Organization does not exist",
|
||||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"Phone number is invalid": "Phone number is invalid",
|
"Phone number is invalid": "Phone number is invalid",
|
||||||
"Session outdated, please login again": "Session outdated, please login again",
|
"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 is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
"The 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 already exists": "Username already exists",
|
||||||
"Username cannot be an email address": "Username cannot be an email address",
|
"Username cannot be an email address": "Username cannot be an email address",
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
"Email is invalid": "El correo electrónico no es válido",
|
"Email is invalid": "El correo electrónico no es válido",
|
||||||
"Empty username.": "Nombre de usuario vacío.",
|
"Empty username.": "Nombre de usuario vacío.",
|
||||||
"FirstName cannot be blank": "El nombre no puede estar en blanco",
|
"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",
|
"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",
|
"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",
|
"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",
|
"Password must have at least 6 characters": "La contraseña debe tener al menos 6 caracteres",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"Phone number is invalid": "El número de teléfono no es válido",
|
"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",
|
"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 is forbidden to sign in, please contact the administrator": "El usuario no está autorizado a iniciar sesión, por favor contacte al administrador",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "El nombre de usuario solo puede contener caracteres alfanuméricos, guiones bajos o guiones, no puede tener guiones o subrayados consecutivos, y no puede comenzar ni terminar con un guión o subrayado.",
|
"The 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 already exists": "El nombre de usuario ya existe",
|
||||||
"Username cannot be an email address": "Nombre de usuario no puede ser una dirección de correo electrónico",
|
"Username cannot be an email address": "Nombre de usuario no puede ser una dirección de correo electrónico",
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
"Email is invalid": "L'adresse e-mail est invalide",
|
"Email is invalid": "L'adresse e-mail est invalide",
|
||||||
"Empty username.": "Nom d'utilisateur vide.",
|
"Empty username.": "Nom d'utilisateur vide.",
|
||||||
"FirstName cannot be blank": "Le prénom ne peut pas être laissé 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",
|
"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",
|
"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",
|
"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",
|
"Password must have at least 6 characters": "Le mot de passe doit comporter au moins 6 caractères",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"Phone number is invalid": "Le numéro de téléphone est invalide",
|
"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",
|
"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 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é.",
|
"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 already exists": "Nom d'utilisateur existe déjà",
|
||||||
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
|
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
"Email is invalid": "Email tidak valid",
|
"Email is invalid": "Email tidak valid",
|
||||||
"Empty username.": "Nama pengguna kosong.",
|
"Empty username.": "Nama pengguna kosong.",
|
||||||
"FirstName cannot be blank": "Nama depan tidak boleh 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",
|
"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",
|
"Multiple accounts with same uid, please check your ldap server": "Beberapa akun dengan uid yang sama, harap periksa server ldap Anda",
|
||||||
"Organization does not exist": "Organisasi tidak ada",
|
"Organization does not exist": "Organisasi tidak ada",
|
||||||
"Password must have at least 6 characters": "Kata sandi harus memiliki minimal 6 karakter",
|
"Password must have at least 6 characters": "Kata sandi harus memiliki minimal 6 karakter",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"Phone number is invalid": "Nomor telepon tidak valid",
|
"Phone number is invalid": "Nomor telepon tidak valid",
|
||||||
"Session outdated, please login again": "Sesi kedaluwarsa, silakan masuk lagi",
|
"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 is forbidden to sign in, please contact the administrator": "Pengguna dilarang masuk, silakan hubungi administrator",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Nama pengguna hanya bisa menggunakan karakter alfanumerik, garis bawah atau tanda hubung, tidak boleh memiliki dua tanda hubung atau garis bawah berurutan, dan tidak boleh diawali atau diakhiri dengan tanda hubung atau garis bawah.",
|
"The 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 already exists": "Nama pengguna sudah ada",
|
||||||
"Username cannot be an email address": "Username tidak bisa menjadi alamat email",
|
"Username cannot be an email address": "Username tidak bisa menjadi alamat email",
|
||||||
|
@ -32,8 +32,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のユーザー名またはパスワードが間違っています",
|
||||||
"LastName cannot be blank": "姓は空白にできません",
|
"LastName cannot be blank": "姓は空白にできません",
|
||||||
"Ldap user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
|
|
||||||
"Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください",
|
"Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください",
|
||||||
"Organization does not exist": "組織は存在しません",
|
"Organization does not exist": "組織は存在しません",
|
||||||
"Password must have at least 6 characters": "パスワードは少なくとも6つの文字が必要です",
|
"Password must have at least 6 characters": "パスワードは少なくとも6つの文字が必要です",
|
||||||
@ -42,6 +42,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": "ユーザー名には電子メールアドレスを使用できません",
|
||||||
|
@ -32,8 +32,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 사용자 이름 또는 암호가 잘못되었습니다",
|
||||||
"LastName cannot be blank": "성은 비어 있을 수 없습니다",
|
"LastName cannot be blank": "성은 비어 있을 수 없습니다",
|
||||||
"Ldap user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
|
|
||||||
"Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요",
|
"Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요",
|
||||||
"Organization does not exist": "조직은 존재하지 않습니다",
|
"Organization does not exist": "조직은 존재하지 않습니다",
|
||||||
"Password must have at least 6 characters": "암호는 적어도 6자 이상이어야 합니다",
|
"Password must have at least 6 characters": "암호는 적어도 6자 이상이어야 합니다",
|
||||||
@ -42,6 +42,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": "사용자 이름은 이메일 주소가 될 수 없습니다",
|
||||||
|
@ -32,8 +32,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",
|
||||||
"LastName cannot be blank": "Фамилия не может быть пустой",
|
"LastName cannot be blank": "Фамилия не может быть пустой",
|
||||||
"Ldap user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
|
|
||||||
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
|
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
|
||||||
"Organization does not exist": "Организация не существует",
|
"Organization does not exist": "Организация не существует",
|
||||||
"Password must have at least 6 characters": "Пароль должен содержать не менее 6 символов",
|
"Password must have at least 6 characters": "Пароль должен содержать не менее 6 символов",
|
||||||
@ -42,6 +42,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": "Имя пользователя не может быть адресом электронной почты",
|
||||||
|
@ -32,8 +32,8 @@
|
|||||||
"Email is invalid": "Địa chỉ email không hợp lệ",
|
"Email is invalid": "Địa chỉ email không hợp lệ",
|
||||||
"Empty username.": "Tên đăng nhập trống.",
|
"Empty username.": "Tên đăng nhập trống.",
|
||||||
"FirstName cannot be blank": "Tên không được để 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",
|
"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",
|
"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",
|
"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ự",
|
"Password must have at least 6 characters": "Mật khẩu phải ít nhất 6 ký tự",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"Phone number is invalid": "Số điện thoại không hợp lệ",
|
"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",
|
"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 is forbidden to sign in, please contact the administrator": "Người dùng bị cấm đăng nhập, vui lòng liên hệ với quản trị viên",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Tên người dùng chỉ có thể chứa các ký tự chữ và số, gạch dưới hoặc gạch ngang, không được có hai ký tự gạch dưới hoặc gạch ngang liền kề và không được bắt đầu hoặc kết thúc bằng dấu gạch dưới hoặc gạch ngang.",
|
"The 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 already exists": "Tên đăng nhập đã tồn tại",
|
||||||
"Username cannot be an email address": "Tên người dùng không thể là địa chỉ email",
|
"Username cannot be an email address": "Tên người dùng không thể là địa chỉ email",
|
||||||
|
@ -32,8 +32,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密码错误",
|
||||||
"LastName cannot be blank": "姓不可以为空",
|
"LastName cannot be blank": "姓不可以为空",
|
||||||
"Ldap user name or password incorrect": "LDAP密码错误",
|
|
||||||
"Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid,请检查您的 LDAP 服务器",
|
"Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid,请检查您的 LDAP 服务器",
|
||||||
"Organization does not exist": "组织不存在",
|
"Organization does not exist": "组织不存在",
|
||||||
"Password must have at least 6 characters": "新密码至少为6位",
|
"Password must have at least 6 characters": "新密码至少为6位",
|
||||||
@ -42,6 +42,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": "用户名不可以是邮箱地址",
|
||||||
|
@ -108,8 +108,8 @@ func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
|
|
||||||
type CasdoorUserInfo struct {
|
type CasdoorUserInfo struct {
|
||||||
Id string `json:"sub"`
|
Id string `json:"sub"`
|
||||||
Name string `json:"name"`
|
Name string `json:"preferred_username,omitempty"`
|
||||||
DisplayName string `json:"preferred_username"`
|
DisplayName string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
AvatarUrl string `json:"picture"`
|
AvatarUrl string `json:"picture"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
@ -61,8 +61,8 @@ func (idp *CustomIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
|
|
||||||
type CustomUserInfo struct {
|
type CustomUserInfo struct {
|
||||||
Id string `json:"sub"`
|
Id string `json:"sub"`
|
||||||
Name string `json:"name"`
|
Name string `json:"preferred_username,omitempty"`
|
||||||
DisplayName string `json:"preferred_username"`
|
DisplayName string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
AvatarUrl string `json:"picture"`
|
AvatarUrl string `json:"picture"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
@ -88,7 +88,7 @@ type GothIdProvider struct {
|
|||||||
Session goth.Session
|
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
|
var idp GothIdProvider
|
||||||
switch providerType {
|
switch providerType {
|
||||||
case "Amazon":
|
case "Amazon":
|
||||||
@ -102,8 +102,13 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Session: &apple.Session{},
|
Session: &apple.Session{},
|
||||||
}
|
}
|
||||||
case "AzureAD":
|
case "AzureAD":
|
||||||
|
domain := "common"
|
||||||
|
if hostUrl != "" {
|
||||||
|
domain = hostUrl
|
||||||
|
}
|
||||||
|
|
||||||
idp = GothIdProvider{
|
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{},
|
Session: &azureadv2.Session{},
|
||||||
}
|
}
|
||||||
case "Auth0":
|
case "Auth0":
|
||||||
|
@ -90,7 +90,7 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
} else if typ == "Douyin" {
|
} else if typ == "Douyin" {
|
||||||
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl)
|
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if isGothSupport(typ) {
|
} else if isGothSupport(typ) {
|
||||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
} else if typ == "Bilibili" {
|
} else if typ == "Bilibili" {
|
||||||
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
|
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
}
|
}
|
||||||
|
@ -159,8 +159,8 @@
|
|||||||
"serverName": "",
|
"serverName": "",
|
||||||
"host": "",
|
"host": "",
|
||||||
"port": 389,
|
"port": 389,
|
||||||
"admin": "",
|
"username": "",
|
||||||
"passwd": "",
|
"password": "",
|
||||||
"baseDn": "",
|
"baseDn": "",
|
||||||
"autoSync": 0,
|
"autoSync": 0,
|
||||||
"lastSync": ""
|
"lastSync": ""
|
||||||
|
121
ldap/server.go
Normal file
121
ldap/server.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// 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(object.CasdoorOrganization, 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(e)
|
||||||
|
}
|
||||||
|
w.Write(res)
|
||||||
|
}
|
135
ldap/util.go
Normal file
135
ldap/util.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// 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 {
|
||||||
|
return "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
for i := nameIndex + 3; filter[i] != ')'; i++ {
|
||||||
|
name = name + string(filter[i])
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
hasPermission, err := object.CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), true, "en")
|
||||||
|
if !hasPermission {
|
||||||
|
log.Printf("ErrMsg = %v", err.Error())
|
||||||
|
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||||
|
}
|
||||||
|
user := object.GetUser(util.GetId(org, name))
|
||||||
|
filteredUsers = append(filteredUsers, user)
|
||||||
|
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 "email":
|
||||||
|
return message.AttributeValue(user.Email)
|
||||||
|
case "mobile":
|
||||||
|
return message.AttributeValue(user.Phone)
|
||||||
|
case "userPassword":
|
||||||
|
return message.AttributeValue(getUserPasswordWithType(user))
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
4
main.go
4
main.go
@ -23,7 +23,7 @@ import (
|
|||||||
_ "github.com/beego/beego/session/redis"
|
_ "github.com/beego/beego/session/redis"
|
||||||
"github.com/casdoor/casdoor/authz"
|
"github.com/casdoor/casdoor/authz"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/controllers"
|
"github.com/casdoor/casdoor/ldap"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/routers"
|
"github.com/casdoor/casdoor/routers"
|
||||||
@ -81,7 +81,7 @@ func main() {
|
|||||||
// logs.SetLevel(logs.LevelInformational)
|
// logs.SetLevel(logs.LevelInformational)
|
||||||
logs.SetLogFuncCall(false)
|
logs.SetLogFuncCall(false)
|
||||||
|
|
||||||
go controllers.StartLdapServer()
|
go ldap.StartLdapServer()
|
||||||
|
|
||||||
beego.Run(fmt.Sprintf(":%v", port))
|
beego.Run(fmt.Sprintf(":%v", port))
|
||||||
}
|
}
|
||||||
|
@ -201,6 +201,16 @@ func (a *Adapter) createTable() {
|
|||||||
panic(err)
|
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))
|
err = a.Engine.Sync2(new(Product))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -80,3 +80,21 @@ func DownloadAndUpload(url string, fullFilePath string, lang string) {
|
|||||||
panic(err)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"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)
|
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
|
||||||
|
}
|
143
object/chat.go
Normal file
143
object/chat.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// 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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Chat) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
|
}
|
@ -188,29 +188,33 @@ func CheckPassword(user *User, password string, lang string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkLdapUserPassword(user *User, password string, lang string) (*User, string) {
|
func checkLdapUserPassword(user *User, password string, lang string) string {
|
||||||
ldaps := GetLdaps(user.Owner)
|
ldaps := GetLdaps(user.Owner)
|
||||||
ldapLoginSuccess := false
|
ldapLoginSuccess := false
|
||||||
|
hit := false
|
||||||
|
|
||||||
for _, ldapServer := range ldaps {
|
for _, ldapServer := range ldaps {
|
||||||
conn, err := ldapServer.GetLdapConn()
|
conn, err := ldapServer.GetLdapConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
SearchFilter := fmt.Sprintf("(&(objectClass=posixAccount)(uid=%s))", user.Name)
|
|
||||||
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn,
|
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
|
||||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
0, 0, false, ldapServer.buildFilterString(user), []string{}, nil)
|
||||||
SearchFilter, []string{}, nil)
|
|
||||||
searchResult, err := conn.Conn.Search(searchReq)
|
searchResult, err := conn.Conn.Search(searchReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(searchResult.Entries) == 0 {
|
if len(searchResult.Entries) == 0 {
|
||||||
continue
|
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
|
dn := searchResult.Entries[0].DN
|
||||||
if err := conn.Conn.Bind(dn, password); err == nil {
|
if err := conn.Conn.Bind(dn, password); err == nil {
|
||||||
ldapLoginSuccess = true
|
ldapLoginSuccess = true
|
||||||
@ -219,9 +223,12 @@ func checkLdapUserPassword(user *User, password string, lang string) (*User, str
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ldapLoginSuccess {
|
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) (*User, string) {
|
||||||
@ -236,10 +243,14 @@ func CheckUserPassword(organization string, username string, password string, la
|
|||||||
|
|
||||||
if user.Ldap != "" {
|
if user.Ldap != "" {
|
||||||
// ONLY for ldap users
|
// 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 {
|
} else {
|
||||||
msg := CheckPassword(user, password, lang)
|
if msg := CheckPassword(user, password, lang); msg != "" {
|
||||||
if msg != "" {
|
|
||||||
return nil, msg
|
return nil, msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,11 +261,13 @@ func filterField(field string) bool {
|
|||||||
return reFieldWhiteList.MatchString(field)
|
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 == "" {
|
if requestUserId == "" {
|
||||||
return false, fmt.Errorf(i18n.Translate(lang, "general:Please login first"))
|
return false, fmt.Errorf(i18n.Translate(lang, "general:Please login first"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userOwner := util.GetOwnerFromId(userId)
|
||||||
|
|
||||||
if userId != "" {
|
if userId != "" {
|
||||||
targetUser := GetUser(userId)
|
targetUser := GetUser(userId)
|
||||||
if targetUser == nil {
|
if targetUser == nil {
|
||||||
@ -340,7 +353,7 @@ func CheckUsername(username string, lang string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckUpdateUser(oldUser *User, user *User, lang string) string {
|
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||||
if user.DisplayName == "" {
|
if user.DisplayName == "" {
|
||||||
return i18n.Translate(lang, "user:Display name cannot be empty")
|
return i18n.Translate(lang, "user:Display name cannot be empty")
|
||||||
}
|
}
|
||||||
|
@ -219,8 +219,8 @@ func initBuiltInLdap() {
|
|||||||
ServerName: "BuildIn LDAP Server",
|
ServerName: "BuildIn LDAP Server",
|
||||||
Host: "example.com",
|
Host: "example.com",
|
||||||
Port: 389,
|
Port: 389,
|
||||||
Admin: "cn=buildin,dc=example,dc=com",
|
Username: "cn=buildin,dc=example,dc=com",
|
||||||
Passwd: "123",
|
Password: "123",
|
||||||
BaseDn: "ou=BuildIn,dc=example,dc=com",
|
BaseDn: "ou=BuildIn,dc=example,dc=com",
|
||||||
AutoSync: 0,
|
AutoSync: 0,
|
||||||
LastSync: "",
|
LastSync: "",
|
||||||
|
402
object/ldap.go
402
object/ldap.go
@ -15,14 +15,7 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/beego/beego"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
goldap "github.com/go-ldap/ldap/v3"
|
|
||||||
"github.com/thanhpk/randstr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Ldap struct {
|
type Ldap struct {
|
||||||
@ -30,263 +23,20 @@ type Ldap struct {
|
|||||||
Owner string `xorm:"varchar(100)" json:"owner"`
|
Owner string `xorm:"varchar(100)" json:"owner"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||||
Host string `xorm:"varchar(100)" json:"host"`
|
Host string `xorm:"varchar(100)" json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `xorm:"int" json:"port"`
|
||||||
EnableSsl bool `xorm:"bool" json:"enableSsl"`
|
EnableSsl bool `xorm:"bool" json:"enableSsl"`
|
||||||
Admin string `xorm:"varchar(100)" json:"admin"`
|
Username string `xorm:"varchar(100)" json:"username"`
|
||||||
Passwd string `xorm:"varchar(100)" json:"passwd"`
|
Password string `xorm:"varchar(100)" json:"password"`
|
||||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
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"`
|
AutoSync int `json:"autoSync"`
|
||||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
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 {
|
func AddLdap(ldap *Ldap) bool {
|
||||||
if len(ldap.Id) == 0 {
|
if len(ldap.Id) == 0 {
|
||||||
ldap.Id = util.GenerateId()
|
ldap.Id = util.GenerateId()
|
||||||
@ -307,12 +57,12 @@ func AddLdap(ldap *Ldap) bool {
|
|||||||
func CheckLdapExist(ldap *Ldap) bool {
|
func CheckLdapExist(ldap *Ldap) bool {
|
||||||
var result []*Ldap
|
var result []*Ldap
|
||||||
err := adapter.Engine.Find(&result, &Ldap{
|
err := adapter.Engine.Find(&result, &Ldap{
|
||||||
Owner: ldap.Owner,
|
Owner: ldap.Owner,
|
||||||
Host: ldap.Host,
|
Host: ldap.Host,
|
||||||
Port: ldap.Port,
|
Port: ldap.Port,
|
||||||
Admin: ldap.Admin,
|
Username: ldap.Username,
|
||||||
Passwd: ldap.Passwd,
|
Password: ldap.Password,
|
||||||
BaseDn: ldap.BaseDn,
|
BaseDn: ldap.BaseDn,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -359,7 +109,7 @@ func UpdateLdap(ldap *Ldap) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -375,123 +125,3 @@ func DeleteLdap(ldap *Ldap) bool {
|
|||||||
|
|
||||||
return affected != 0
|
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,7 +82,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
users, err := conn.GetLdapUsers(ldap.BaseDn)
|
users, err := conn.GetLdapUsers(ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||||
continue
|
continue
|
||||||
@ -112,3 +112,10 @@ func (l *LdapAutoSynchronizer) LdapAutoSynchronizerStartUpAll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateLdapSyncTime(ldapId string) {
|
||||||
|
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
403
object/ldap_conn.go
Normal file
403
object/ldap_conn.go
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
// 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/beego/beego"
|
||||||
|
"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
|
||||||
|
Uid string
|
||||||
|
Cn string
|
||||||
|
GidNumber string
|
||||||
|
// Gcn string
|
||||||
|
Uuid string
|
||||||
|
DisplayName 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"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
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 LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||||
|
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,
|
||||||
|
DisplayName: user.DisplayName,
|
||||||
|
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
||||||
|
Phone: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
||||||
|
Address: util.ReturnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
|
||||||
|
var existUsers []LdapRespUser
|
||||||
|
var failedUsers []LdapRespUser
|
||||||
|
var uuids []string
|
||||||
|
|
||||||
|
for _, user := range respUsers {
|
||||||
|
uuids = append(uuids, user.Uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
existUuids := GetExistUuids(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.Username, ",") {
|
||||||
|
if strings.Contains(admin, "ou=") {
|
||||||
|
ou = append(ou, admin[3:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag := strings.Join(ou, ".")
|
||||||
|
|
||||||
|
for _, respUser := range respUsers {
|
||||||
|
found := false
|
||||||
|
if len(existUuids) > 0 {
|
||||||
|
for _, existUuid := range existUuids {
|
||||||
|
if respUser.Uuid == existUuid {
|
||||||
|
existUsers = append(existUsers, respUser)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
newUser := &User{
|
||||||
|
Owner: owner,
|
||||||
|
Name: respUser.buildLdapUserName(),
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
DisplayName: respUser.buildLdapDisplayName(),
|
||||||
|
Avatar: organization.DefaultAvatar,
|
||||||
|
Email: respUser.Email,
|
||||||
|
Phone: respUser.Phone,
|
||||||
|
Address: []string{respUser.Address},
|
||||||
|
Affiliation: affiliation,
|
||||||
|
Tag: tag,
|
||||||
|
Score: beego.AppConfig.DefaultInt("initScore", 2000),
|
||||||
|
Ldap: respUser.Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
affected := AddUser(newUser)
|
||||||
|
if !affected {
|
||||||
|
failedUsers = append(failedUsers, respUser)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &existUsers, &failedUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetExistUuids(owner string, uuids []string) []string {
|
||||||
|
var users []User
|
||||||
|
var existUuids []string
|
||||||
|
existUuidSet := make(map[string]struct{})
|
||||||
|
|
||||||
|
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&users)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(users) > 0 {
|
||||||
|
for _, result := range users {
|
||||||
|
existUuidSet[result.Ldap] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for uuid := range existUuidSet {
|
||||||
|
existUuids = append(existUuids, uuid)
|
||||||
|
}
|
||||||
|
return existUuids
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ldapUser *LdapRespUser) 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ldapUser.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ldapUser *LdapRespUser) buildLdapDisplayName() string {
|
||||||
|
if ldapUser.DisplayName != "" {
|
||||||
|
return ldapUser.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
return ldapUser.Cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ldap *Ldap) buildFilterString(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 "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
|
|
||||||
}
|
|
||||||
}
|
|
148
object/message.go
Normal file
148
object/message.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// 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"`
|
||||||
|
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, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&Message{})
|
||||||
|
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 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)
|
||||||
|
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 (p *Message) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
|
}
|
@ -18,6 +18,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@ -43,6 +44,26 @@ type OidcDiscovery struct {
|
|||||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
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) {
|
func getOriginFromHost(host string) (string, string) {
|
||||||
origin := conf.GetConfigString("origin")
|
origin := conf.GetConfigString("origin")
|
||||||
if origin != "" {
|
if origin != "" {
|
||||||
@ -52,6 +73,8 @@ func getOriginFromHost(host string) (string, string) {
|
|||||||
protocol := "https://"
|
protocol := "https://"
|
||||||
if strings.HasPrefix(host, "localhost") {
|
if strings.HasPrefix(host, "localhost") {
|
||||||
protocol = "http://"
|
protocol = "http://"
|
||||||
|
} else if isIpAddress(host) {
|
||||||
|
protocol = "http://"
|
||||||
}
|
}
|
||||||
|
|
||||||
if host == "localhost:8000" {
|
if host == "localhost:8000" {
|
||||||
|
@ -29,7 +29,10 @@ import (
|
|||||||
func getEnforcer(permission *Permission) *casbin.Enforcer {
|
func getEnforcer(permission *Permission) *casbin.Enforcer {
|
||||||
tableName := "permission_rule"
|
tableName := "permission_rule"
|
||||||
if len(permission.Adapter) != 0 {
|
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")
|
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||||
driverName := conf.GetConfigString("driverName")
|
driverName := conf.GetConfigString("driverName")
|
||||||
@ -130,7 +133,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
|||||||
for _, subUser := range roleObj.Users {
|
for _, subUser := range roleObj.Users {
|
||||||
if domainExist {
|
if domainExist {
|
||||||
for _, domain := range permission.Domains {
|
for _, domain := range permission.Domains {
|
||||||
groupingPolicies = append(groupingPolicies, []string{subUser, domain, role, "", "", permissionId})
|
groupingPolicies = append(groupingPolicies, []string{subUser, role, domain, "", "", permissionId})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
groupingPolicies = append(groupingPolicies, []string{subUser, role, "", "", "", permissionId})
|
groupingPolicies = append(groupingPolicies, []string{subUser, role, "", "", "", permissionId})
|
||||||
@ -140,7 +143,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
|||||||
for _, subRole := range roleObj.Roles {
|
for _, subRole := range roleObj.Roles {
|
||||||
if domainExist {
|
if domainExist {
|
||||||
for _, domain := range permission.Domains {
|
for _, domain := range permission.Domains {
|
||||||
groupingPolicies = append(groupingPolicies, []string{subRole, domain, role, "", "", permissionId})
|
groupingPolicies = append(groupingPolicies, []string{subRole, role, domain, "", "", permissionId})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
groupingPolicies = append(groupingPolicies, []string{subRole, role, "", "", "", permissionId})
|
groupingPolicies = append(groupingPolicies, []string{subRole, role, "", "", "", permissionId})
|
||||||
|
@ -30,7 +30,10 @@ func TestProduct(t *testing.T) {
|
|||||||
product := GetProduct("admin/product_123")
|
product := GetProduct("admin/product_123")
|
||||||
provider := getProvider(product.Owner, "provider_pay_alipay")
|
provider := getProvider(product.Owner, "provider_pay_alipay")
|
||||||
cert := getCert(product.Owner, "cert-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()
|
paymentName := util.GenerateTimeId()
|
||||||
returnUrl := ""
|
returnUrl := ""
|
||||||
|
@ -221,6 +221,10 @@ func UpdateProvider(id string, provider *Provider) bool {
|
|||||||
if provider.ClientSecret2 == "***" {
|
if provider.ClientSecret2 == "***" {
|
||||||
session = session.Omit("client_secret2")
|
session = session.Omit("client_secret2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||||
|
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||||
|
|
||||||
affected, err := session.Update(provider)
|
affected, err := session.Update(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -230,6 +234,9 @@ func UpdateProvider(id string, provider *Provider) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddProvider(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)
|
affected, err := adapter.Engine.Insert(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -256,7 +263,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 {
|
if pProvider == nil {
|
||||||
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||||
}
|
}
|
||||||
|
@ -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")
|
authnStatement.CreateElement("saml:AuthnContext").CreateElement("saml:AuthnContextClassRef").SetText("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")
|
||||||
|
|
||||||
attributes := assertion.CreateElement("saml:AttributeStatement")
|
attributes := assertion.CreateElement("saml:AttributeStatement")
|
||||||
|
|
||||||
email := attributes.CreateElement("saml:Attribute")
|
email := attributes.CreateElement("saml:Attribute")
|
||||||
email.CreateAttr("Name", "Email")
|
email.CreateAttr("Name", "Email")
|
||||||
email.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
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)
|
email.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Email)
|
||||||
|
|
||||||
name := attributes.CreateElement("saml:Attribute")
|
name := attributes.CreateElement("saml:Attribute")
|
||||||
name.CreateAttr("Name", "Name")
|
name.CreateAttr("Name", "Name")
|
||||||
name.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
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)
|
name.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Name)
|
||||||
|
|
||||||
displayName := attributes.CreateElement("saml:Attribute")
|
displayName := attributes.CreateElement("saml:Attribute")
|
||||||
displayName.CreateAttr("Name", "DisplayName")
|
displayName.CreateAttr("Name", "DisplayName")
|
||||||
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
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)
|
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
|
return samlResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,29 +23,32 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
|
||||||
"github.com/casdoor/casdoor/i18n"
|
"github.com/casdoor/casdoor/i18n"
|
||||||
saml2 "github.com/russellhaering/gosaml2"
|
saml2 "github.com/russellhaering/gosaml2"
|
||||||
dsig "github.com/russellhaering/goxmldsig"
|
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)
|
samlResponse, _ = url.QueryUnescape(samlResponse)
|
||||||
sp, err := buildSp(&Provider{Type: providerType}, samlResponse)
|
sp, err := buildSp(provider, samlResponse, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
|
||||||
|
|
||||||
|
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
return assertionInfo.NameID, 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)
|
provider := GetProvider(id)
|
||||||
if provider.Category != "SAML" {
|
if provider.Category != "SAML" {
|
||||||
return "", "", fmt.Errorf(i18n.Translate(lang, "saml_sp:provider %s's category is not SAML"), provider.Name)
|
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 {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
@ -67,35 +70,22 @@ func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method stri
|
|||||||
return auth, method, nil
|
return auth, method, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
|
func buildSp(provider *Provider, samlResponse string, host string) (*saml2.SAMLServiceProvider, error) {
|
||||||
origin := conf.GetConfigString("origin")
|
_, origin := getOriginFromHost(host)
|
||||||
|
|
||||||
certStore := dsig.MemoryX509CertificateStore{
|
certStore, err := buildSpCertificateStore(provider, samlResponse)
|
||||||
Roots: []*x509.Certificate{},
|
|
||||||
}
|
|
||||||
|
|
||||||
certEncodedData := ""
|
|
||||||
if samlResponse != "" {
|
|
||||||
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
|
||||||
} else if provider.IdP != "" {
|
|
||||||
certEncodedData = provider.IdP
|
|
||||||
}
|
|
||||||
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
idpCert, err := x509.ParseCertificate(certData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
certStore.Roots = append(certStore.Roots, idpCert)
|
|
||||||
sp := &saml2.SAMLServiceProvider{
|
sp := &saml2.SAMLServiceProvider{
|
||||||
ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin),
|
ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin),
|
||||||
AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin),
|
AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin),
|
||||||
IDPCertificateStore: &certStore,
|
|
||||||
SignAuthnRequests: false,
|
SignAuthnRequests: false,
|
||||||
|
IDPCertificateStore: &certStore,
|
||||||
SPKeyStore: dsig.RandomKeyStoreForTest(),
|
SPKeyStore: dsig.RandomKeyStoreForTest(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if provider.Endpoint != "" {
|
if provider.Endpoint != "" {
|
||||||
sp.IdentityProviderSSOURL = provider.Endpoint
|
sp.IdentityProviderSSOURL = provider.Endpoint
|
||||||
sp.IdentityProviderIssuer = provider.IssuerUrl
|
sp.IdentityProviderIssuer = provider.IssuerUrl
|
||||||
@ -104,10 +94,45 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
|
|||||||
sp.SignAuthnRequests = true
|
sp.SignAuthnRequests = true
|
||||||
sp.SPKeyStore = buildSpKeyStore()
|
sp.SPKeyStore = buildSpKeyStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
return sp, nil
|
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)
|
de, err := base64.StdEncoding.DecodeString(samlResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -122,14 +147,3 @@ func parseSamlResponse(samlResponse string, providerType string) string {
|
|||||||
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
|
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
|
||||||
return res[1]
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -51,7 +51,7 @@ type Token struct {
|
|||||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
User string `xorm:"varchar(100)" json:"user"`
|
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"`
|
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||||
ExpiresIn int `json:"expiresIn"`
|
ExpiresIn int `json:"expiresIn"`
|
||||||
@ -362,7 +362,8 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
}
|
}
|
||||||
|
|
||||||
token.CodeIsUsed = true
|
token.CodeIsUsed = true
|
||||||
updateUsedByCode(token)
|
go updateUsedByCode(token)
|
||||||
|
|
||||||
tokenWrapper := &TokenWrapper{
|
tokenWrapper := &TokenWrapper{
|
||||||
AccessToken: token.AccessToken,
|
AccessToken: token.AccessToken,
|
||||||
IdToken: token.AccessToken,
|
IdToken: token.AccessToken,
|
||||||
@ -613,7 +614,8 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
|||||||
nullUser := &User{
|
nullUser := &User{
|
||||||
Owner: application.Owner,
|
Owner: application.Owner,
|
||||||
Id: application.GetId(),
|
Id: application.GetId(),
|
||||||
Name: fmt.Sprintf("app/%s", application.Name),
|
Name: application.Name,
|
||||||
|
Type: "application",
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", scope, host)
|
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", scope, host)
|
||||||
|
@ -15,10 +15,12 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
@ -48,6 +50,7 @@ type User struct {
|
|||||||
EmailVerified bool `json:"emailVerified"`
|
EmailVerified bool `json:"emailVerified"`
|
||||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||||
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||||
|
Region string `xorm:"varchar(100)" json:"region"`
|
||||||
Location string `xorm:"varchar(100)" json:"location"`
|
Location string `xorm:"varchar(100)" json:"location"`
|
||||||
Address []string `json:"address"`
|
Address []string `json:"address"`
|
||||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||||
@ -57,7 +60,6 @@ type User struct {
|
|||||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
Region string `xorm:"varchar(100)" json:"region"`
|
|
||||||
Language string `xorm:"varchar(100)" json:"language"`
|
Language string `xorm:"varchar(100)" json:"language"`
|
||||||
Gender string `xorm:"varchar(100)" json:"gender"`
|
Gender string `xorm:"varchar(100)" json:"gender"`
|
||||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||||
@ -449,7 +451,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
|||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
columns = []string{
|
columns = []string{
|
||||||
"owner", "display_name", "avatar",
|
"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",
|
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
||||||
"signin_wrong_times", "last_signin_wrong_time",
|
"signin_wrong_times", "last_signin_wrong_time",
|
||||||
}
|
}
|
||||||
@ -513,7 +515,10 @@ func AddUser(user *User) bool {
|
|||||||
user.UpdateUserHash()
|
user.UpdateUserHash()
|
||||||
user.PreHash = user.Hash
|
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
|
user.Ranking = GetUserCount(user.Owner, "", "") + 1
|
||||||
|
|
||||||
@ -693,3 +698,40 @@ func userChangeTrigger(oldName string, newName string) error {
|
|||||||
|
|
||||||
return session.Commit()
|
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
|
||||||
|
}
|
||||||
|
@ -131,6 +131,12 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
|
|||||||
if user.DisplayName == "" {
|
if user.DisplayName == "" {
|
||||||
user.DisplayName = userInfo.DisplayName
|
user.DisplayName = userInfo.DisplayName
|
||||||
}
|
}
|
||||||
|
} else if user.DisplayName == "" {
|
||||||
|
if userInfo.Username != "" {
|
||||||
|
user.DisplayName = userInfo.Username
|
||||||
|
} else {
|
||||||
|
user.DisplayName = userInfo.Id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if userInfo.Email != "" {
|
if userInfo.Email != "" {
|
||||||
propertyName := fmt.Sprintf("oauth_%s_email", providerType)
|
propertyName := fmt.Sprintf("oauth_%s_email", providerType)
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@ -38,6 +39,11 @@ const (
|
|||||||
timeoutError = 3
|
timeoutError = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VerifyTypePhone = "phone"
|
||||||
|
VerifyTypeEmail = "email"
|
||||||
|
)
|
||||||
|
|
||||||
type VerificationRecord struct {
|
type VerificationRecord struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@ -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
|
// From Casnode/object/validateCode.go line 116
|
||||||
var stdNums = []byte("0123456789")
|
var stdNums = []byte("0123456789")
|
||||||
|
|
||||||
|
@ -28,21 +28,21 @@ type AlipayPaymentProvider struct {
|
|||||||
Client *alipay.Client
|
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{}
|
pp := &AlipayPaymentProvider{}
|
||||||
|
|
||||||
client, err := alipay.NewClient(appId, appPrivateKey, true)
|
client, err := alipay.NewClient(appId, appPrivateKey, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.SetCertSnByContent([]byte(appCertificate), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
|
err = client.SetCertSnByContent([]byte(appCertificate), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pp.Client = client
|
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) {
|
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)
|
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" {
|
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" {
|
} 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
|
||||||
}
|
}
|
||||||
|
102
pp/wechatpay.go
Normal file
102
pp/wechatpay.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// 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 pp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/go-pay/gopay"
|
||||||
|
"github.com/go-pay/gopay/wechat/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WechatPaymentProvider struct {
|
||||||
|
ClientV3 *wechat.ClientV3
|
||||||
|
appId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWechatPaymentProvider(appId string, mchId string, mchCertSerialNumber string, apiV3Key string, privateKey string) (*WechatPaymentProvider, error) {
|
||||||
|
pp := &WechatPaymentProvider{appId: appId}
|
||||||
|
|
||||||
|
clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = clientV3.AutoVerifySign()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pp.ClientV3 = clientV3
|
||||||
|
return pp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||||
|
// pp.Client.DebugSwitch = gopay.DebugOn
|
||||||
|
|
||||||
|
bm := gopay.BodyMap{}
|
||||||
|
|
||||||
|
bm.Set("providerName", providerName)
|
||||||
|
bm.Set("productName", productName)
|
||||||
|
|
||||||
|
bm.Set("return_url", returnUrl)
|
||||||
|
bm.Set("notify_url", notifyUrl)
|
||||||
|
|
||||||
|
bm.Set("body", productDisplayName)
|
||||||
|
bm.Set("out_trade_no", paymentName)
|
||||||
|
bm.Set("total_fee", getPriceString(price))
|
||||||
|
|
||||||
|
wechatRsp, err := pp.ClientV3.V3TransactionJsapi(context.Background(), bm)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
payUrl := fmt.Sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect", pp.appId, wechatRsp.Response.PrepayId)
|
||||||
|
return payUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||||
|
bm, err := wechat.V3ParseNotifyToBodyMap(request)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
providerName := bm.Get("providerName")
|
||||||
|
productName := bm.Get("productName")
|
||||||
|
|
||||||
|
productDisplayName := bm.Get("body")
|
||||||
|
paymentName := bm.Get("out_trade_no")
|
||||||
|
price := util.ParseFloat(bm.Get("total_fee"))
|
||||||
|
|
||||||
|
notifyReq, err := wechat.V3ParseNotify(request)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := pp.ClientV3.WxPublicKey()
|
||||||
|
|
||||||
|
err = notifyReq.VerifySignByPK(cert)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *WechatPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
@ -115,6 +115,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "GET:GetEmailAndPhone")
|
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "GET:GetEmailAndPhone")
|
||||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||||
|
beego.Router("/api/verify-code", &controllers.ApiController{}, "POST:VerifyCode")
|
||||||
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
||||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||||
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||||
@ -188,6 +189,18 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||||
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
||||||
|
|
||||||
|
beego.Router("/api/get-chats", &controllers.ApiController{}, "GET:GetChats")
|
||||||
|
beego.Router("/api/get-chat", &controllers.ApiController{}, "GET:GetChat")
|
||||||
|
beego.Router("/api/update-chat", &controllers.ApiController{}, "POST:UpdateChat")
|
||||||
|
beego.Router("/api/add-chat", &controllers.ApiController{}, "POST:AddChat")
|
||||||
|
beego.Router("/api/delete-chat", &controllers.ApiController{}, "POST:DeleteChat")
|
||||||
|
|
||||||
|
beego.Router("/api/get-messages", &controllers.ApiController{}, "GET:GetMessages")
|
||||||
|
beego.Router("/api/get-message", &controllers.ApiController{}, "GET:GetMessage")
|
||||||
|
beego.Router("/api/update-message", &controllers.ApiController{}, "POST:UpdateMessage")
|
||||||
|
beego.Router("/api/add-message", &controllers.ApiController{}, "POST:AddMessage")
|
||||||
|
beego.Router("/api/delete-message", &controllers.ApiController{}, "POST:DeleteMessage")
|
||||||
|
|
||||||
beego.Router("/api/get-products", &controllers.ApiController{}, "GET:GetProducts")
|
beego.Router("/api/get-products", &controllers.ApiController{}, "GET:GetProducts")
|
||||||
beego.Router("/api/get-product", &controllers.ApiController{}, "GET:GetProduct")
|
beego.Router("/api/get-product", &controllers.ApiController{}, "GET:GetProduct")
|
||||||
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
|
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
|
||||||
|
@ -2058,22 +2058,13 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"System API"
|
"System API"
|
||||||
],
|
],
|
||||||
"description": "get user's system info",
|
"description": "get system info like CPU and memory usage",
|
||||||
"operationId": "ApiController.GetSystemInfo",
|
"operationId": "ApiController.GetSystemInfo",
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"in": "query",
|
|
||||||
"name": "id",
|
|
||||||
"description": "The id ( owner/name ) of the user",
|
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The Response object",
|
"description": "The Response object",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/object.SystemInfo"
|
"$ref": "#/definitions/util.SystemInfo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2180,6 +2171,12 @@
|
|||||||
"name": "phone",
|
"name": "phone",
|
||||||
"description": "The phone of the user",
|
"description": "The phone of the user",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "userId",
|
||||||
|
"description": "The userId of the user",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -2323,11 +2320,14 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"System API"
|
"System API"
|
||||||
],
|
],
|
||||||
"description": "get local git repo's latest release version info",
|
"description": "get version info like Casdoor release version and commit ID",
|
||||||
"operationId": "ApiController.GetVersionInfo",
|
"operationId": "ApiController.GetVersionInfo",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "{string} local latest version hash of Casdoor"
|
"description": "The Response object",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/util.VersionInfo"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3494,6 +3494,23 @@
|
|||||||
"operationId": "ApiController.UploadResource"
|
"operationId": "ApiController.UploadResource"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/user": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Account API"
|
||||||
|
],
|
||||||
|
"description": "return Laravel compatible user information according to OAuth 2.0",
|
||||||
|
"operationId": "ApiController.UserInfo2",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "The Response object",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/LaravelResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/userinfo": {
|
"/api/userinfo": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -3627,14 +3644,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"2268.0xc000528cf0.false": {
|
"2306.0xc0003a4480.false": {
|
||||||
"title": "false",
|
"title": "false",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"2302.0xc000528d20.false": {
|
"2340.0xc0003a44b0.false": {
|
||||||
"title": "false",
|
"title": "false",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"LaravelResponse": {
|
||||||
|
"title": "LaravelResponse",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"Response": {
|
"Response": {
|
||||||
"title": "Response",
|
"title": "Response",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@ -3682,6 +3703,9 @@
|
|||||||
"captchaType": {
|
"captchaType": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"clientId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"clientSecret": {
|
"clientSecret": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -3758,10 +3782,10 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/2268.0xc000528cf0.false"
|
"$ref": "#/definitions/2306.0xc0003a4480.false"
|
||||||
},
|
},
|
||||||
"data2": {
|
"data2": {
|
||||||
"$ref": "#/definitions/2302.0xc000528d20.false"
|
"$ref": "#/definitions/2340.0xc0003a44b0.false"
|
||||||
},
|
},
|
||||||
"msg": {
|
"msg": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -4830,10 +4854,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"object.SystemInfo": {
|
|
||||||
"title": "SystemInfo",
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"object.TableColumn": {
|
"object.TableColumn": {
|
||||||
"title": "TableColumn",
|
"title": "TableColumn",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -5475,6 +5495,43 @@
|
|||||||
"title": "CredentialCreationResponse",
|
"title": "CredentialCreationResponse",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"util.SystemInfo": {
|
||||||
|
"title": "SystemInfo",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"cpuUsage": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"memoryTotal": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"memoryUsed": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"util.VersionInfo": {
|
||||||
|
"title": "VersionInfo",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"commitId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"commitOffset": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"webauthn.Credential": {
|
"webauthn.Credential": {
|
||||||
"title": "Credential",
|
"title": "Credential",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
@ -1340,19 +1340,13 @@ paths:
|
|||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- System API
|
- System API
|
||||||
description: get user's system info
|
description: get system info like CPU and memory usage
|
||||||
operationId: ApiController.GetSystemInfo
|
operationId: ApiController.GetSystemInfo
|
||||||
parameters:
|
|
||||||
- in: query
|
|
||||||
name: id
|
|
||||||
description: The id ( owner/name ) of the user
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.SystemInfo'
|
$ref: '#/definitions/util.SystemInfo'
|
||||||
/api/get-token:
|
/api/get-token:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1423,6 +1417,10 @@ paths:
|
|||||||
name: phone
|
name: phone
|
||||||
description: The phone of the user
|
description: The phone of the user
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: userId
|
||||||
|
description: The userId of the user
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
@ -1515,11 +1513,13 @@ paths:
|
|||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- System API
|
- System API
|
||||||
description: get local git repo's latest release version info
|
description: get version info like Casdoor release version and commit ID
|
||||||
operationId: ApiController.GetVersionInfo
|
operationId: ApiController.GetVersionInfo
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: '{string} local latest version hash of Casdoor'
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/util.VersionInfo'
|
||||||
/api/get-webhook:
|
/api/get-webhook:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -2288,6 +2288,17 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Resource API
|
- Resource API
|
||||||
operationId: ApiController.UploadResource
|
operationId: ApiController.UploadResource
|
||||||
|
/api/user:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Account API
|
||||||
|
description: return Laravel compatible user information according to OAuth 2.0
|
||||||
|
operationId: ApiController.UserInfo2
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The Response object
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/LaravelResponse'
|
||||||
/api/userinfo:
|
/api/userinfo:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -2374,12 +2385,15 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
definitions:
|
definitions:
|
||||||
2268.0xc000528cf0.false:
|
2306.0xc0003a4480.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
2302.0xc000528d20.false:
|
2340.0xc0003a44b0.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
|
LaravelResponse:
|
||||||
|
title: LaravelResponse
|
||||||
|
type: object
|
||||||
Response:
|
Response:
|
||||||
title: Response
|
title: Response
|
||||||
type: object
|
type: object
|
||||||
@ -2413,6 +2427,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
captchaType:
|
captchaType:
|
||||||
type: string
|
type: string
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
clientSecret:
|
clientSecret:
|
||||||
type: string
|
type: string
|
||||||
code:
|
code:
|
||||||
@ -2464,9 +2480,9 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/2268.0xc000528cf0.false'
|
$ref: '#/definitions/2306.0xc0003a4480.false'
|
||||||
data2:
|
data2:
|
||||||
$ref: '#/definitions/2302.0xc000528d20.false'
|
$ref: '#/definitions/2340.0xc0003a44b0.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -3183,9 +3199,6 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
user:
|
user:
|
||||||
type: string
|
type: string
|
||||||
object.SystemInfo:
|
|
||||||
title: SystemInfo
|
|
||||||
type: object
|
|
||||||
object.TableColumn:
|
object.TableColumn:
|
||||||
title: TableColumn
|
title: TableColumn
|
||||||
type: object
|
type: object
|
||||||
@ -3617,6 +3630,32 @@ definitions:
|
|||||||
protocol.CredentialCreationResponse:
|
protocol.CredentialCreationResponse:
|
||||||
title: CredentialCreationResponse
|
title: CredentialCreationResponse
|
||||||
type: object
|
type: object
|
||||||
|
util.SystemInfo:
|
||||||
|
title: SystemInfo
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cpuUsage:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
memoryTotal:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
memoryUsed:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
util.VersionInfo:
|
||||||
|
title: VersionInfo
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
commitId:
|
||||||
|
type: string
|
||||||
|
commitOffset:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
webauthn.Credential:
|
webauthn.Credential:
|
||||||
title: Credential
|
title: Credential
|
||||||
type: object
|
type: object
|
||||||
|
@ -30,3 +30,12 @@ func ContainsString(values []string, val string) bool {
|
|||||||
sort.Strings(values)
|
sort.Strings(values)
|
||||||
return sort.SearchStrings(values, val) != len(values)
|
return sort.SearchStrings(values, val) != len(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReturnAnyNotEmpty(strs ...string) string {
|
||||||
|
for _, str := range strs {
|
||||||
|
if str != "" {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -95,6 +95,15 @@ func GetOwnerAndNameFromId(id string) (string, string) {
|
|||||||
return tokens[0], tokens[1]
|
return tokens[0], tokens[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetOwnerFromId(id string) string {
|
||||||
|
tokens := strings.Split(id, "/")
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
panic(errors.New("GetOwnerAndNameFromId() error, wrong token count for ID: " + id))
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens[0]
|
||||||
|
}
|
||||||
|
|
||||||
func GetOwnerAndNameFromIdNoCheck(id string) (string, string) {
|
func GetOwnerAndNameFromIdNoCheck(id string) (string, string) {
|
||||||
tokens := strings.SplitN(id, "/", 2)
|
tokens := strings.SplitN(id, "/", 2)
|
||||||
return tokens[0], tokens[1]
|
return tokens[0], tokens[1]
|
||||||
@ -250,3 +259,11 @@ func maskString(str string) string {
|
|||||||
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str)-2), str[len(str)-1])
|
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str)-2), str[len(str)-1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetEndPoint remove scheme from url
|
||||||
|
func GetEndPoint(endpoint string) string {
|
||||||
|
for _, prefix := range []string{"https://", "http://"} {
|
||||||
|
endpoint = strings.TrimPrefix(endpoint, prefix)
|
||||||
|
}
|
||||||
|
return endpoint
|
||||||
|
}
|
||||||
|
@ -59,7 +59,7 @@ func getMemoryUsage() (uint64, uint64, error) {
|
|||||||
var m runtime.MemStats
|
var m runtime.MemStats
|
||||||
runtime.ReadMemStats(&m)
|
runtime.ReadMemStats(&m)
|
||||||
|
|
||||||
return m.TotalAlloc, virtualMem.Total, nil
|
return m.Alloc, virtualMem.Total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSystemInfo() (*SystemInfo, error) {
|
func GetSystemInfo() (*SystemInfo, error) {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/cssinjs": "^1.5.6",
|
"@ant-design/cssinjs": "^1.8.1",
|
||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
"@craco/craco": "^6.4.5",
|
"@craco/craco": "^6.4.5",
|
||||||
"@crowdin/cli": "^3.7.10",
|
"@crowdin/cli": "^3.7.10",
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"@testing-library/user-event": "^7.1.2",
|
||||||
"antd": "5.1.6",
|
"antd": "5.2.3",
|
||||||
"antd-token-previewer": "^1.1.0-22",
|
"antd-token-previewer": "^1.1.0-22",
|
||||||
"codemirror": "^5.61.1",
|
"codemirror": "^5.61.1",
|
||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
|
@ -21,7 +21,7 @@ import i18next from "i18next";
|
|||||||
|
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
import * as ModelBackend from "./backend/ModelBackend";
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
import PolicyTable from "./common/PoliciyTable";
|
import PolicyTable from "./table/PoliciyTable";
|
||||||
require("codemirror/theme/material-darker.css");
|
require("codemirror/theme/material-darker.css");
|
||||||
require("codemirror/mode/javascript/javascript");
|
require("codemirror/mode/javascript/javascript");
|
||||||
|
|
||||||
@ -112,6 +112,7 @@ class AdapterEditPage extends React.Component {
|
|||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {
|
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {
|
||||||
this.getModels(value);
|
this.getModels(value);
|
||||||
this.updateAdapterField("organization", value);
|
this.updateAdapterField("organization", value);
|
||||||
|
this.updateAdapterField("owner", value);
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
@ -266,7 +267,7 @@ class AdapterEditPage extends React.Component {
|
|||||||
|
|
||||||
submitAdapterEdit(willExist) {
|
submitAdapterEdit(willExist) {
|
||||||
const adapter = Setting.deepCopy(this.state.adapter);
|
const adapter = Setting.deepCopy(this.state.adapter);
|
||||||
AdapterBackend.updateAdapter(this.state.adapter.owner, this.state.adapterName, adapter)
|
AdapterBackend.updateAdapter(this.state.owner, this.state.adapterName, adapter)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
|
@ -17,7 +17,7 @@ import "./App.less";
|
|||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||||
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||||
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
||||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||||
import OrganizationListPage from "./OrganizationListPage";
|
import OrganizationListPage from "./OrganizationListPage";
|
||||||
@ -44,6 +44,11 @@ import SyncerListPage from "./SyncerListPage";
|
|||||||
import SyncerEditPage from "./SyncerEditPage";
|
import SyncerEditPage from "./SyncerEditPage";
|
||||||
import CertListPage from "./CertListPage";
|
import CertListPage from "./CertListPage";
|
||||||
import CertEditPage from "./CertEditPage";
|
import CertEditPage from "./CertEditPage";
|
||||||
|
import ChatListPage from "./ChatListPage";
|
||||||
|
import ChatEditPage from "./ChatEditPage";
|
||||||
|
import ChatPage from "./ChatPage";
|
||||||
|
import MessageEditPage from "./MessageEditPage";
|
||||||
|
import MessageListPage from "./MessageListPage";
|
||||||
import ProductListPage from "./ProductListPage";
|
import ProductListPage from "./ProductListPage";
|
||||||
import ProductEditPage from "./ProductEditPage";
|
import ProductEditPage from "./ProductEditPage";
|
||||||
import ProductBuyPage from "./ProductBuyPage";
|
import ProductBuyPage from "./ProductBuyPage";
|
||||||
@ -52,7 +57,7 @@ import PaymentEditPage from "./PaymentEditPage";
|
|||||||
import PaymentResultPage from "./PaymentResultPage";
|
import PaymentResultPage from "./PaymentResultPage";
|
||||||
import AccountPage from "./account/AccountPage";
|
import AccountPage from "./account/AccountPage";
|
||||||
import HomePage from "./basic/HomePage";
|
import HomePage from "./basic/HomePage";
|
||||||
import CustomGithubCorner from "./CustomGithubCorner";
|
import CustomGithubCorner from "./common/CustomGithubCorner";
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
|
|
||||||
import * as Auth from "./auth/Auth";
|
import * as Auth from "./auth/Auth";
|
||||||
@ -60,7 +65,7 @@ import EntryPage from "./EntryPage";
|
|||||||
import ResultPage from "./auth/ResultPage";
|
import ResultPage from "./auth/ResultPage";
|
||||||
import * as AuthBackend from "./auth/AuthBackend";
|
import * as AuthBackend from "./auth/AuthBackend";
|
||||||
import AuthCallback from "./auth/AuthCallback";
|
import AuthCallback from "./auth/AuthCallback";
|
||||||
import SelectLanguageBox from "./SelectLanguageBox";
|
import LanguageSelect from "./common/select/LanguageSelect";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||||
import SamlCallback from "./auth/SamlCallback";
|
import SamlCallback from "./auth/SamlCallback";
|
||||||
@ -70,7 +75,7 @@ import SystemInfo from "./SystemInfo";
|
|||||||
import AdapterListPage from "./AdapterListPage";
|
import AdapterListPage from "./AdapterListPage";
|
||||||
import AdapterEditPage from "./AdapterEditPage";
|
import AdapterEditPage from "./AdapterEditPage";
|
||||||
import {withTranslation} from "react-i18next";
|
import {withTranslation} from "react-i18next";
|
||||||
import SelectThemeBox from "./SelectThemeBox";
|
import ThemeSelect from "./common/select/ThemeSelect";
|
||||||
import SessionListPage from "./SessionListPage";
|
import SessionListPage from "./SessionListPage";
|
||||||
|
|
||||||
const {Header, Footer, Content} = Layout;
|
const {Header, Footer, Content} = Layout;
|
||||||
@ -135,16 +140,22 @@ class App extends Component {
|
|||||||
this.setState({selectedMenuKey: "/applications"});
|
this.setState({selectedMenuKey: "/applications"});
|
||||||
} else if (uri.includes("/resources")) {
|
} else if (uri.includes("/resources")) {
|
||||||
this.setState({selectedMenuKey: "/resources"});
|
this.setState({selectedMenuKey: "/resources"});
|
||||||
} else if (uri.includes("/tokens")) {
|
|
||||||
this.setState({selectedMenuKey: "/tokens"});
|
|
||||||
} else if (uri.includes("/records")) {
|
} else if (uri.includes("/records")) {
|
||||||
this.setState({selectedMenuKey: "/records"});
|
this.setState({selectedMenuKey: "/records"});
|
||||||
|
} else if (uri.includes("/tokens")) {
|
||||||
|
this.setState({selectedMenuKey: "/tokens"});
|
||||||
|
} else if (uri.includes("/sessions")) {
|
||||||
|
this.setState({selectedMenuKey: "/sessions"});
|
||||||
} else if (uri.includes("/webhooks")) {
|
} else if (uri.includes("/webhooks")) {
|
||||||
this.setState({selectedMenuKey: "/webhooks"});
|
this.setState({selectedMenuKey: "/webhooks"});
|
||||||
} else if (uri.includes("/syncers")) {
|
} else if (uri.includes("/syncers")) {
|
||||||
this.setState({selectedMenuKey: "/syncers"});
|
this.setState({selectedMenuKey: "/syncers"});
|
||||||
} else if (uri.includes("/certs")) {
|
} else if (uri.includes("/certs")) {
|
||||||
this.setState({selectedMenuKey: "/certs"});
|
this.setState({selectedMenuKey: "/certs"});
|
||||||
|
} else if (uri.includes("/chats")) {
|
||||||
|
this.setState({selectedMenuKey: "/chats"});
|
||||||
|
} else if (uri.includes("/messages")) {
|
||||||
|
this.setState({selectedMenuKey: "/messages"});
|
||||||
} else if (uri.includes("/products")) {
|
} else if (uri.includes("/products")) {
|
||||||
this.setState({selectedMenuKey: "/products"});
|
this.setState({selectedMenuKey: "/products"});
|
||||||
} else if (uri.includes("/payments")) {
|
} else if (uri.includes("/payments")) {
|
||||||
@ -315,12 +326,17 @@ class App extends Component {
|
|||||||
items.push(Setting.getItem(<><SettingOutlined /> {i18next.t("account:My Account")}</>,
|
items.push(Setting.getItem(<><SettingOutlined /> {i18next.t("account:My Account")}</>,
|
||||||
"/account"
|
"/account"
|
||||||
));
|
));
|
||||||
|
items.push(Setting.getItem(<><CommentOutlined /> {i18next.t("account:Chats & Messages")}</>,
|
||||||
|
"/chat"
|
||||||
|
));
|
||||||
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
||||||
"/logout"));
|
"/logout"));
|
||||||
|
|
||||||
const onClick = (e) => {
|
const onClick = (e) => {
|
||||||
if (e.key === "/account") {
|
if (e.key === "/account") {
|
||||||
this.props.history.push("/account");
|
this.props.history.push("/account");
|
||||||
|
} else if (e.key === "/chat") {
|
||||||
|
this.props.history.push("/chat");
|
||||||
} else if (e.key === "/logout") {
|
} else if (e.key === "/logout") {
|
||||||
this.logout();
|
this.logout();
|
||||||
}
|
}
|
||||||
@ -352,7 +368,7 @@ class App extends Component {
|
|||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{this.renderRightDropdown()}
|
{this.renderRightDropdown()}
|
||||||
<SelectThemeBox
|
<ThemeSelect
|
||||||
themeAlgorithm={this.state.themeAlgorithm}
|
themeAlgorithm={this.state.themeAlgorithm}
|
||||||
onChange={(nextThemeAlgorithm) => {
|
onChange={(nextThemeAlgorithm) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -360,7 +376,7 @@ class App extends Component {
|
|||||||
logo: this.getLogo(nextThemeAlgorithm),
|
logo: this.getLogo(nextThemeAlgorithm),
|
||||||
});
|
});
|
||||||
}} />
|
}} />
|
||||||
<SelectLanguageBox languages={this.state.account.organization.languages} />
|
<LanguageSelect languages={this.state.account.organization.languages} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -413,6 +429,14 @@ class App extends Component {
|
|||||||
"/providers"
|
"/providers"
|
||||||
));
|
));
|
||||||
|
|
||||||
|
res.push(Setting.getItem(<Link to="/chats">{i18next.t("general:Chats")}</Link>,
|
||||||
|
"/chats"
|
||||||
|
));
|
||||||
|
|
||||||
|
res.push(Setting.getItem(<Link to="/messages">{i18next.t("general:Messages")}</Link>,
|
||||||
|
"/messages"
|
||||||
|
));
|
||||||
|
|
||||||
res.push(Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>,
|
res.push(Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>,
|
||||||
"/resources"
|
"/resources"
|
||||||
));
|
));
|
||||||
@ -527,6 +551,11 @@ class App extends Component {
|
|||||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/chat" render={(props) => this.renderLoginIfNotLoggedIn(<ChatPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/messages" render={(props) => this.renderLoginIfNotLoggedIn(<MessageListPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/messages/:messageName" render={(props) => this.renderLoginIfNotLoggedIn(<MessageEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||||
@ -600,7 +629,7 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
</Header>
|
</Header>
|
||||||
<Content style={{display: "flex", flexDirection: "column"}} >
|
<Content style={{display: "flex", flexDirection: "column"}} >
|
||||||
{Setting.isMobile() ?
|
{(Setting.isMobile() || window.location.pathname === "/chat") ?
|
||||||
this.renderRouter() :
|
this.renderRouter() :
|
||||||
<Card className="content-warp-card">
|
<Card className="content-warp-card">
|
||||||
{this.renderRouter()}
|
{this.renderRouter()}
|
||||||
|
@ -25,9 +25,9 @@ import * as ResourceBackend from "./backend/ResourceBackend";
|
|||||||
import SignupPage from "./auth/SignupPage";
|
import SignupPage from "./auth/SignupPage";
|
||||||
import LoginPage from "./auth/LoginPage";
|
import LoginPage from "./auth/LoginPage";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import UrlTable from "./UrlTable";
|
import UrlTable from "./table/UrlTable";
|
||||||
import ProviderTable from "./ProviderTable";
|
import ProviderTable from "./table/ProviderTable";
|
||||||
import SignupTable from "./SignupTable";
|
import SignupTable from "./table/SignupTable";
|
||||||
import PromptPage from "./auth/PromptPage";
|
import PromptPage from "./auth/PromptPage";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
|
|
||||||
@ -867,6 +867,8 @@ class ApplicationEditPage extends React.Component {
|
|||||||
|
|
||||||
submitApplicationEdit(willExist) {
|
submitApplicationEdit(willExist) {
|
||||||
const application = Setting.deepCopy(this.state.application);
|
const application = Setting.deepCopy(this.state.application);
|
||||||
|
application.providers = application.providers?.filter(provider => this.state.providers.map(provider => provider.name).includes(provider.name));
|
||||||
|
|
||||||
ApplicationBackend.updateApplication("admin", this.state.applicationName, application)
|
ApplicationBackend.updateApplication("admin", this.state.applicationName, application)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
|
190
web/src/ChatBox.js
Normal file
190
web/src/ChatBox.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Avatar, Input, List} from "antd";
|
||||||
|
import {CopyOutlined, DislikeOutlined, LikeOutlined, SendOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
|
const {TextArea} = Input;
|
||||||
|
|
||||||
|
class ChatBox extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
inputValue: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
this.listContainerRef = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps.messages !== this.props.messages) {
|
||||||
|
this.scrollToListItem(this.props.messages.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (this.state.inputValue !== "") {
|
||||||
|
this.send(this.state.inputValue);
|
||||||
|
this.setState({inputValue: ""});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scrollToListItem(index) {
|
||||||
|
const listContainerElement = this.listContainerRef.current;
|
||||||
|
|
||||||
|
if (!listContainerElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetItem = listContainerElement.querySelector(
|
||||||
|
`#chatbox-list-item-${index}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!targetItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollDistance = targetItem.offsetTop - listContainerElement.offsetTop;
|
||||||
|
|
||||||
|
listContainerElement.scrollTo({
|
||||||
|
top: scrollDistance,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
send = (text) => {
|
||||||
|
this.props.sendMessage(text);
|
||||||
|
this.setState({inputValue: ""});
|
||||||
|
};
|
||||||
|
|
||||||
|
renderList() {
|
||||||
|
return (
|
||||||
|
<div ref={this.listContainerRef} style={{position: "relative", maxHeight: "calc(100vh - 140px)", overflowY: "auto"}}>
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={this.props.messages === undefined ? undefined : [...this.props.messages, {}]}
|
||||||
|
renderItem={(item, index) => {
|
||||||
|
if (Object.keys(item).length === 0 && item.constructor === Object) {
|
||||||
|
return <List.Item id={`chatbox-list-item-${index}`} style={{
|
||||||
|
height: "160px",
|
||||||
|
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
|
||||||
|
borderBottom: "1px solid rgb(229, 229, 229)",
|
||||||
|
position: "relative",
|
||||||
|
}} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List.Item id={`chatbox-list-item-${index}`} style={{
|
||||||
|
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
|
||||||
|
borderBottom: "1px solid rgb(229, 229, 229)",
|
||||||
|
position: "relative",
|
||||||
|
}}>
|
||||||
|
<div style={{width: "800px", margin: "0 auto", position: "relative"}}>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={<Avatar style={{width: "30px", height: "30px", borderRadius: "3px"}} src={item.author === `${this.props.account.owner}/${this.props.account.name}` ? this.props.account.avatar : "https://cdn.casbin.com/casdoor/resource/built-in/admin/gpt.png"} />}
|
||||||
|
title={<div style={{fontSize: "16px", fontWeight: "normal", lineHeight: "24px", marginTop: "-15px", marginLeft: "5px", marginRight: "80px"}}>{item.text}</div>}
|
||||||
|
/>
|
||||||
|
<div style={{position: "absolute", top: "0px", right: "0px"}}
|
||||||
|
>
|
||||||
|
<CopyOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
|
||||||
|
<LikeOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
|
||||||
|
<DislikeOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: "120px",
|
||||||
|
background: "linear-gradient(transparent 0%, rgba(255, 255, 255, 0.8) 50%, white 100%)",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInput() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
bottom: "90px",
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{position: "relative", width: "760px", marginLeft: "-280px"}}>
|
||||||
|
<TextArea
|
||||||
|
placeholder={"Send a message..."}
|
||||||
|
autoSize={{maxRows: 8}}
|
||||||
|
value={this.state.inputValue}
|
||||||
|
onChange={(e) => this.setState({inputValue: e.target.value})}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
style={{
|
||||||
|
fontSize: "16px",
|
||||||
|
fontWeight: "normal",
|
||||||
|
lineHeight: "24px",
|
||||||
|
width: "770px",
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
borderColor: "rgb(229,229,229)",
|
||||||
|
boxShadow: "0 0 15px rgba(0, 0, 0, 0.1)",
|
||||||
|
paddingLeft: "17px",
|
||||||
|
paddingRight: "17px",
|
||||||
|
paddingTop: "12px",
|
||||||
|
paddingBottom: "12px",
|
||||||
|
}}
|
||||||
|
suffix={<SendOutlined style={{color: "rgb(210,210,217"}} onClick={() => this.send(this.state.inputValue)} />}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
<SendOutlined
|
||||||
|
style={{
|
||||||
|
color: this.state.inputValue === "" ? "rgb(210,210,217)" : "rgb(142,142,160)",
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "17px",
|
||||||
|
right: "17px",
|
||||||
|
}}
|
||||||
|
onClick={() => this.send(this.state.inputValue)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.renderList()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.renderInput()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatBox;
|
243
web/src/ChatEditPage.js
Normal file
243
web/src/ChatEditPage.js
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Button, Card, Col, Input, Row, Select} from "antd";
|
||||||
|
import * as ChatBackend from "./backend/ChatBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
class ChatEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
chatName: props.match.params.chatName,
|
||||||
|
chat: null,
|
||||||
|
organizations: [],
|
||||||
|
users: [],
|
||||||
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getChat();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getChat() {
|
||||||
|
ChatBackend.getChat("admin", this.state.chatName)
|
||||||
|
.then((chat) => {
|
||||||
|
this.setState({
|
||||||
|
chat: chat,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getUsers(chat.organization);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUsers(organizationName) {
|
||||||
|
UserBackend.getUsers(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
users: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseChatField(key, value) {
|
||||||
|
if ([].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChatField(key, value) {
|
||||||
|
value = this.parseChatField(key, value);
|
||||||
|
|
||||||
|
const chat = this.state.chat;
|
||||||
|
chat[key] = value;
|
||||||
|
this.setState({
|
||||||
|
chat: chat,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChat() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("chat:New Chat") : i18next.t("chat:Edit Chat")}
|
||||||
|
<Button onClick={() => this.submitChatEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitChatEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteChat()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||||
|
<Row style={{marginTop: "10px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.organization} onChange={(value => {this.updateChatField("organization", value);})}
|
||||||
|
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.chat.name} onChange={e => {
|
||||||
|
this.updateChatField("name", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.chat.displayName} onChange={e => {
|
||||||
|
this.updateChatField("displayName", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.type} onChange={(value => {
|
||||||
|
this.updateChatField("type", value);
|
||||||
|
})}
|
||||||
|
options={[
|
||||||
|
{value: "Single", name: i18next.t("chat:Single")},
|
||||||
|
{value: "Group", name: i18next.t("chat:Group")},
|
||||||
|
{value: "AI", name: i18next.t("chat:AI")},
|
||||||
|
].map((item) => Setting.getOption(item.name, item.value))}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.chat.category} onChange={e => {
|
||||||
|
this.updateChatField("category", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("chat:User1"), i18next.t("chat:User1 - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user1} onChange={(value => {this.updateChatField("user1", value);})}
|
||||||
|
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("chat:User2"), i18next.t("chat:User2 - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user2} onChange={(value => {this.updateChatField("user2", value);})}
|
||||||
|
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Users"), i18next.t("chat:Users - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select mode="tags" style={{width: "100%"}} value={this.state.chat.users}
|
||||||
|
onChange={(value => {this.updateChatField("users", value);})}
|
||||||
|
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitChatEdit(willExist) {
|
||||||
|
const chat = Setting.deepCopy(this.state.chat);
|
||||||
|
ChatBackend.updateChat(this.state.chat.owner, this.state.chatName, chat)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
|
this.setState({
|
||||||
|
chatName: this.state.chat.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push("/chats");
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/chats/${this.state.chat.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
|
this.updateChatField("name", this.state.chatName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChat() {
|
||||||
|
ChatBackend.deleteChat(this.state.chat)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push("/chats");
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.chat !== null ? this.renderChat() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
|
<Button size="large" onClick={() => this.submitChatEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitChatEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteChat()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatEditPage;
|
294
web/src/ChatListPage.js
Normal file
294
web/src/ChatListPage.js
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Button, Table} from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as ChatBackend from "./backend/ChatBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
|
class ChatListPage extends BaseListPage {
|
||||||
|
newChat() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "admin", // this.props.account.applicationName,
|
||||||
|
name: `chat_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
updatedTime: moment().format(),
|
||||||
|
organization: this.props.account.owner,
|
||||||
|
displayName: `New Chat - ${randomName}`,
|
||||||
|
type: "Single",
|
||||||
|
category: "Chat Category - 1",
|
||||||
|
user1: `${this.props.account.owner}/${this.props.account.name}`,
|
||||||
|
user2: "",
|
||||||
|
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||||
|
messageCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
addChat() {
|
||||||
|
const newChat = this.newChat();
|
||||||
|
ChatBackend.addChat(newChat)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push({pathname: `/chats/${newChat.name}`, mode: "add"});
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChat(i) {
|
||||||
|
ChatBackend.deleteChat(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(chats) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: "organization",
|
||||||
|
key: "organization",
|
||||||
|
width: "150px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("organization"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/organizations/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
width: "120px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("name"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/chats/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: "createdTime",
|
||||||
|
key: "createdTime",
|
||||||
|
width: "150px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Updated time"),
|
||||||
|
dataIndex: "updatedTime",
|
||||||
|
key: "updatedTime",
|
||||||
|
width: "15 0px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: "displayName",
|
||||||
|
key: "displayName",
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("displayName"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:Type"),
|
||||||
|
dataIndex: "type",
|
||||||
|
key: "type",
|
||||||
|
width: "110px",
|
||||||
|
sorter: true,
|
||||||
|
filterMultiple: false,
|
||||||
|
filters: [
|
||||||
|
{text: "Single", value: "Single"},
|
||||||
|
{text: "Group", value: "Group"},
|
||||||
|
{text: "AI", value: "AI"},
|
||||||
|
],
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return i18next.t(`chat:${text}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:Category"),
|
||||||
|
dataIndex: "category",
|
||||||
|
key: "category",
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("category"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("chat:User1"),
|
||||||
|
dataIndex: "user1",
|
||||||
|
key: "user1",
|
||||||
|
width: "120px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("user1"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/users/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("chat:User2"),
|
||||||
|
dataIndex: "user2",
|
||||||
|
key: "user2",
|
||||||
|
width: "120px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("user2"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/users/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Users"),
|
||||||
|
dataIndex: "users",
|
||||||
|
key: "users",
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("users"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getTags(text, "users");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("chat:Message count"),
|
||||||
|
dataIndex: "messageCount",
|
||||||
|
key: "messageCount",
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("messageCount"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: "",
|
||||||
|
key: "op",
|
||||||
|
width: "170px",
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/chats/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<PopconfirmModal
|
||||||
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteChat(index)}
|
||||||
|
>
|
||||||
|
</PopconfirmModal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.pagination.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={chats} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Chats")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addChat.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.category !== undefined && params.category !== null) {
|
||||||
|
field = "category";
|
||||||
|
value = params.category;
|
||||||
|
} else if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
ChatBackend.getChats("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (Setting.isResponseDenied(res)) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
isAuthorized: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatListPage;
|
100
web/src/ChatMenu.js
Normal file
100
web/src/ChatMenu.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Menu} from "antd";
|
||||||
|
import {LayoutOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
|
class ChatMenu extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const items = this.chatsToItems(this.props.chats);
|
||||||
|
const openKeys = items.map((item) => item.key);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
openKeys: openKeys,
|
||||||
|
selectedKeys: ["0-0"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
chatsToItems(chats) {
|
||||||
|
const categories = {};
|
||||||
|
chats.forEach((chat) => {
|
||||||
|
if (!categories[chat.category]) {
|
||||||
|
categories[chat.category] = [];
|
||||||
|
}
|
||||||
|
categories[chat.category].push(chat);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.keys(categories).map((category, index) => {
|
||||||
|
return {
|
||||||
|
key: `${index}`,
|
||||||
|
icon: <LayoutOutlined />,
|
||||||
|
label: category,
|
||||||
|
children: categories[category].map((chat, chatIndex) => {
|
||||||
|
const globalChatIndex = chats.indexOf(chat);
|
||||||
|
return {
|
||||||
|
key: `${index}-${chatIndex}`,
|
||||||
|
index: globalChatIndex,
|
||||||
|
label: chat.displayName,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (info) => {
|
||||||
|
const [categoryIndex, chatIndex] = info.selectedKeys[0].split("-").map(Number);
|
||||||
|
const selectedItem = this.chatsToItems(this.props.chats)[categoryIndex].children[chatIndex];
|
||||||
|
this.setState({selectedKeys: [`${categoryIndex}-${chatIndex}`]});
|
||||||
|
|
||||||
|
if (this.props.onSelect) {
|
||||||
|
this.props.onSelect(selectedItem.index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getRootSubmenuKeys(items) {
|
||||||
|
return items.map((item, index) => `${index}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenChange = (keys) => {
|
||||||
|
const items = this.chatsToItems(this.props.chats);
|
||||||
|
const rootSubmenuKeys = this.getRootSubmenuKeys(items);
|
||||||
|
const latestOpenKey = keys.find((key) => this.state.openKeys.indexOf(key) === -1);
|
||||||
|
|
||||||
|
if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
|
||||||
|
this.setState({openKeys: keys});
|
||||||
|
} else {
|
||||||
|
this.setState({openKeys: latestOpenKey ? [latestOpenKey] : []});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const items = this.chatsToItems(this.props.chats);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
mode="inline"
|
||||||
|
openKeys={this.state.openKeys}
|
||||||
|
selectedKeys={this.state.selectedKeys}
|
||||||
|
onOpenChange={this.onOpenChange}
|
||||||
|
onSelect={this.onSelect}
|
||||||
|
items={items}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatMenu;
|
199
web/src/ChatPage.js
Normal file
199
web/src/ChatPage.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Spin} from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import ChatMenu from "./ChatMenu";
|
||||||
|
import ChatBox from "./ChatBox";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as ChatBackend from "./backend/ChatBackend";
|
||||||
|
import * as MessageBackend from "./backend/MessageBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
|
class ChatPage extends BaseListPage {
|
||||||
|
newChat() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "admin", // this.props.account.applicationName,
|
||||||
|
name: `chat_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
updatedTime: moment().format(),
|
||||||
|
organization: this.props.account.owner,
|
||||||
|
displayName: `New Chat - ${randomName}`,
|
||||||
|
type: "Single",
|
||||||
|
category: "Chat Category - 1",
|
||||||
|
user1: `${this.props.account.owner}/${this.props.account.name}`,
|
||||||
|
user2: "",
|
||||||
|
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||||
|
messageCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// addChat() {
|
||||||
|
// const newChat = this.newChat();
|
||||||
|
// ChatBackend.addChat(newChat)
|
||||||
|
// .then((res) => {
|
||||||
|
// if (res.status === "ok") {
|
||||||
|
// this.props.history.push({pathname: `/chats/${newChat.name}`, mode: "add"});
|
||||||
|
// Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
|
// } else {
|
||||||
|
// Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .catch(error => {
|
||||||
|
// Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// deleteChat(i) {
|
||||||
|
// ChatBackend.deleteChat(this.state.data[i])
|
||||||
|
// .then((res) => {
|
||||||
|
// if (res.status === "ok") {
|
||||||
|
// Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
|
// this.setState({
|
||||||
|
// data: Setting.deleteRow(this.state.data, i),
|
||||||
|
// pagination: {total: this.state.pagination.total - 1},
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .catch(error => {
|
||||||
|
// Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
newMessage(text) {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "admin", // this.props.account.messagename,
|
||||||
|
name: `message_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
organization: this.props.account.owner,
|
||||||
|
chat: this.state.chatName,
|
||||||
|
author: `${this.props.account.owner}/${this.props.account.name}`,
|
||||||
|
text: text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(text) {
|
||||||
|
const newMessage = this.newMessage(text);
|
||||||
|
MessageBackend.addMessage(newMessage)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.getMessages(this.state.chatName);
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessages(chatName) {
|
||||||
|
MessageBackend.getChatMessages(chatName)
|
||||||
|
.then((messages) => {
|
||||||
|
this.setState({
|
||||||
|
messages: messages,
|
||||||
|
});
|
||||||
|
|
||||||
|
Setting.scrollToDiv(`chatbox-list-item-${messages.length}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(chats) {
|
||||||
|
return (this.state.loading) ? <Spin size="large" style={{marginLeft: "50%", marginTop: "10%"}} /> : (
|
||||||
|
(
|
||||||
|
<div style={{display: "flex", height: "calc(100vh - 140px)"}}>
|
||||||
|
<div style={{width: "250px", height: "100%", backgroundColor: "white", borderRight: "1px solid rgb(245,245,245)"}}>
|
||||||
|
<ChatMenu chats={chats} onSelect={(i) => {
|
||||||
|
const chat = chats[i];
|
||||||
|
this.getMessages(chat.name);
|
||||||
|
this.setState({
|
||||||
|
chatName: chat.name,
|
||||||
|
});
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
<div style={{flex: 1, height: "100%", backgroundColor: "white", position: "relative"}}>
|
||||||
|
<div style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundImage: "url(https://cdn.casbin.org/img/casdoor-logo_1185x256.png)",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
backgroundSize: "200px auto",
|
||||||
|
backgroundBlendMode: "luminosity",
|
||||||
|
filter: "grayscale(80%) brightness(140%) contrast(90%)",
|
||||||
|
opacity: 0.5,
|
||||||
|
}}>
|
||||||
|
</div>
|
||||||
|
<ChatBox messages={this.state.messages} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.category !== undefined && params.category !== null) {
|
||||||
|
field = "category";
|
||||||
|
value = params.category;
|
||||||
|
} else if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
ChatBackend.getChats("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
|
||||||
|
const chats = res.data;
|
||||||
|
if (this.state.chatName === undefined && chats.length > 0) {
|
||||||
|
const chat = chats[0];
|
||||||
|
this.getMessages(chat.name);
|
||||||
|
this.setState({
|
||||||
|
chatName: chat.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Setting.isResponseDenied(res)) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
isAuthorized: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatPage;
|
@ -69,7 +69,7 @@ class EntryPage extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||||
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
<Spin size="large" spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
@ -84,7 +84,7 @@ class EntryPage extends React.Component {
|
|||||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
|
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -166,13 +166,37 @@ class LdapEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
|
{Setting.getLabel(i18next.t("ldap:Search Filter"), i18next.t("ldap:Search Filter - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={21}>
|
||||||
|
<Input value={this.state.ldap.filter} onChange={e => {
|
||||||
|
this.updateLdapField("filter", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
|
{Setting.getLabel(i18next.t("ldap:Filter fields"), i18next.t("ldap:Filter fields - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={21}>
|
||||||
|
<Select value={this.state.ldap.filterFields ?? []} style={{width: "100%"}} mode={"multiple"} options={[
|
||||||
|
{value: "uid", label: "uid"},
|
||||||
|
{value: "mail", label: "Email"},
|
||||||
|
{value: "mobile", label: "mobile"},
|
||||||
|
].map((item) => Setting.getOption(item.label, item.value))} onChange={value => {
|
||||||
|
this.updateLdapField("filterFields", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}}>
|
<Row style={{marginTop: "20px"}}>
|
||||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
{Setting.getLabel(i18next.t("ldap:Admin"), i18next.t("ldap:Admin - Tooltip"))} :
|
{Setting.getLabel(i18next.t("ldap:Admin"), i18next.t("ldap:Admin - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={21}>
|
<Col span={21}>
|
||||||
<Input value={this.state.ldap.admin} onChange={e => {
|
<Input value={this.state.ldap.username} onChange={e => {
|
||||||
this.updateLdapField("admin", e.target.value);
|
this.updateLdapField("username", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -182,9 +206,9 @@ class LdapEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={21}>
|
<Col span={21}>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} value={this.state.ldap.passwd}
|
iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} value={this.state.ldap.password}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
this.updateLdapField("passwd", e.target.value);
|
this.updateLdapField("password", e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -1,192 +0,0 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import {Link} from "react-router-dom";
|
|
||||||
import {Button, Col, Row, Table} from "antd";
|
|
||||||
import * as Setting from "./Setting";
|
|
||||||
import * as LdapBackend from "./backend/LdapBackend";
|
|
||||||
import i18next from "i18next";
|
|
||||||
import PopconfirmModal from "./PopconfirmModal";
|
|
||||||
|
|
||||||
class LdapListPage extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
ldaps: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
|
||||||
this.getLdaps();
|
|
||||||
}
|
|
||||||
|
|
||||||
getLdaps() {
|
|
||||||
LdapBackend.getLdaps("")
|
|
||||||
.then((res) => {
|
|
||||||
let ldapsData = [];
|
|
||||||
if (res.status === "ok") {
|
|
||||||
ldapsData = res.data;
|
|
||||||
} else {
|
|
||||||
Setting.showMessage("error", res.msg);
|
|
||||||
}
|
|
||||||
this.setState((prevState) => {
|
|
||||||
prevState.ldaps = ldapsData;
|
|
||||||
return prevState;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteLdap(index) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTable(ldaps) {
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: i18next.t("ldap:Server name"),
|
|
||||||
dataIndex: "serverName",
|
|
||||||
key: "serverName",
|
|
||||||
width: "200px",
|
|
||||||
sorter: (a, b) => a.serverName.localeCompare(b.serverName),
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return (
|
|
||||||
<Link to={`/ldaps/${record.id}`}>
|
|
||||||
{text}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18next.t("general:Organization"),
|
|
||||||
dataIndex: "owner",
|
|
||||||
key: "owner",
|
|
||||||
width: "140px",
|
|
||||||
sorter: (a, b) => a.owner.localeCompare(b.owner),
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return (
|
|
||||||
<Link to={`/organizations/${text}`}>
|
|
||||||
{text}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18next.t("ldap:Server"),
|
|
||||||
dataIndex: "host",
|
|
||||||
key: "host",
|
|
||||||
ellipsis: true,
|
|
||||||
sorter: (a, b) => a.host.localeCompare(b.host),
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return `${text}:${record.port}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18next.t("ldap:Base DN"),
|
|
||||||
dataIndex: "baseDn",
|
|
||||||
key: "baseDn",
|
|
||||||
ellipsis: true,
|
|
||||||
sorter: (a, b) => a.baseDn.localeCompare(b.baseDn),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18next.t("ldap:Admin"),
|
|
||||||
dataIndex: "admin",
|
|
||||||
key: "admin",
|
|
||||||
ellipsis: true,
|
|
||||||
sorter: (a, b) => a.admin.localeCompare(b.admin),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18next.t("ldap:Auto Sync"),
|
|
||||||
dataIndex: "autoSync",
|
|
||||||
key: "autoSync",
|
|
||||||
width: "100px",
|
|
||||||
sorter: (a, b) => a.autoSync.localeCompare(b.autoSync),
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
|
|
||||||
<span style={{color: "#52c41a"}}>{text + " mins"}</span>);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18next.t("ldap:Last Sync"),
|
|
||||||
dataIndex: "lastSync",
|
|
||||||
key: "lastSync",
|
|
||||||
ellipsis: true,
|
|
||||||
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18next.t("general:Action"),
|
|
||||||
dataIndex: "",
|
|
||||||
key: "op",
|
|
||||||
width: "240px",
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
|
||||||
type="primary"
|
|
||||||
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("general:Sync")}</Button>
|
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
|
||||||
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
|
|
||||||
<PopconfirmModal
|
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
|
|
||||||
onConfirm={() => this.deleteLdap(index)}
|
|
||||||
>
|
|
||||||
</PopconfirmModal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Table columns={columns} dataSource={ldaps} rowKey="id" size="middle" bordered
|
|
||||||
pagination={{pageSize: 100}}
|
|
||||||
title={() => (
|
|
||||||
<div>
|
|
||||||
<span>{i18next.t("general:LDAPs")}</span>
|
|
||||||
<Button type="primary" size="small" style={{marginLeft: "10px"}}
|
|
||||||
onClick={() => {
|
|
||||||
this.addLdap();
|
|
||||||
}}>{i18next.t("general:Add")}</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
loading={ldaps === null}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Row style={{width: "100%"}}>
|
|
||||||
<Col span={1}>
|
|
||||||
</Col>
|
|
||||||
<Col span={22}>
|
|
||||||
{
|
|
||||||
this.renderTable(this.state.ldaps)
|
|
||||||
}
|
|
||||||
</Col>
|
|
||||||
<Col span={1}>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LdapListPage;
|
|
@ -13,10 +13,11 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Col, Popconfirm, Row, Table} from "antd";
|
import {Button, Popconfirm, Table} from "antd";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as LdapBackend from "./backend/LdapBackend";
|
import * as LdapBackend from "./backend/LdapBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
class LdapSyncPage extends React.Component {
|
class LdapSyncPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -77,9 +78,8 @@ class LdapSyncPage extends React.Component {
|
|||||||
LdapBackend.getLdap(this.state.organizationName, this.state.ldapId)
|
LdapBackend.getLdap(this.state.organizationName, this.state.ldapId)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState((prevState) => {
|
this.setState({
|
||||||
prevState.ldap = res.data;
|
ldap: res.data,
|
||||||
return prevState;
|
|
||||||
});
|
});
|
||||||
this.getLdapUser();
|
this.getLdapUser();
|
||||||
} else {
|
} else {
|
||||||
@ -139,22 +139,46 @@ class LdapSyncPage extends React.Component {
|
|||||||
dataIndex: "cn",
|
dataIndex: "cn",
|
||||||
key: "cn",
|
key: "cn",
|
||||||
sorter: (a, b) => a.cn.localeCompare(b.cn),
|
sorter: (a, b) => a.cn.localeCompare(b.cn),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (<div style={{display: "flex", justifyContent: "space-between"}}>
|
||||||
|
<div>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
{this.state.existUuids.includes(record.uuid) ?
|
||||||
|
Setting.getTag("green", i18next.t("ldap:synced")) :
|
||||||
|
Setting.getTag("red", i18next.t("ldap:unsynced"))
|
||||||
|
}
|
||||||
|
</div>);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("ldap:UidNumber / Uid"),
|
title: "Uid",
|
||||||
|
dataIndex: "uid",
|
||||||
|
key: "uid",
|
||||||
|
sorter: (a, b) => a.uid.localeCompare(b.uid),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
this.state.existUuids.includes(record.uuid) ?
|
||||||
|
<Link to={`/users/${this.state.organizationName}/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link> :
|
||||||
|
text
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "UidNumber",
|
||||||
dataIndex: "uidNumber",
|
dataIndex: "uidNumber",
|
||||||
key: "uidNumber",
|
key: "uidNumber",
|
||||||
width: "200px",
|
|
||||||
sorter: (a, b) => a.uidNumber.localeCompare(b.uidNumber),
|
sorter: (a, b) => a.uidNumber.localeCompare(b.uidNumber),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return `${text} / ${record.uid}`;
|
return text;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("ldap:Group ID"),
|
title: i18next.t("ldap:Group ID"),
|
||||||
dataIndex: "groupId",
|
dataIndex: "groupId",
|
||||||
key: "groupId",
|
key: "groupId",
|
||||||
width: "140px",
|
|
||||||
sorter: (a, b) => a.groupId.localeCompare(b.groupId),
|
sorter: (a, b) => a.groupId.localeCompare(b.groupId),
|
||||||
filters: this.buildFilter(this.state.users, "groupId"),
|
filters: this.buildFilter(this.state.users, "groupId"),
|
||||||
onFilter: (value, record) => record.groupId.indexOf(value) === 0,
|
onFilter: (value, record) => record.groupId.indexOf(value) === 0,
|
||||||
@ -163,14 +187,12 @@ class LdapSyncPage extends React.Component {
|
|||||||
title: i18next.t("general:Email"),
|
title: i18next.t("general:Email"),
|
||||||
dataIndex: "email",
|
dataIndex: "email",
|
||||||
key: "email",
|
key: "email",
|
||||||
width: "240px",
|
|
||||||
sorter: (a, b) => a.email.localeCompare(b.email),
|
sorter: (a, b) => a.email.localeCompare(b.email),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Phone"),
|
title: i18next.t("general:Phone"),
|
||||||
dataIndex: "phone",
|
dataIndex: "phone",
|
||||||
key: "phone",
|
key: "phone",
|
||||||
width: "160px",
|
|
||||||
sorter: (a, b) => a.phone.localeCompare(b.phone),
|
sorter: (a, b) => a.phone.localeCompare(b.phone),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -183,9 +205,8 @@ class LdapSyncPage extends React.Component {
|
|||||||
|
|
||||||
const rowSelection = {
|
const rowSelection = {
|
||||||
onChange: (selectedRowKeys, selectedRows) => {
|
onChange: (selectedRowKeys, selectedRows) => {
|
||||||
this.setState(prevState => {
|
this.setState({
|
||||||
prevState.selectedUsers = selectedRows;
|
selectedUsers: selectedRows,
|
||||||
return prevState;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getCheckboxProps: record => ({
|
getCheckboxProps: record => ({
|
||||||
@ -194,42 +215,36 @@ class LdapSyncPage extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered size="small"
|
||||||
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered
|
pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}}
|
||||||
pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}}
|
title={() => (
|
||||||
title={() => (
|
<div>
|
||||||
<div>
|
{this.state.ldap?.serverName}
|
||||||
<span>{this.state.ldap?.serverName}</span>
|
<Popconfirm placement={"right"} disabled={this.state.selectedUsers.length === 0}
|
||||||
<Popconfirm placement={"right"}
|
title={"Please confirm to sync selected users"}
|
||||||
title={"Please confirm to sync selected users"}
|
onConfirm={() => this.syncUsers()}
|
||||||
onConfirm={() => this.syncUsers()}
|
>
|
||||||
>
|
<Button type="primary" style={{marginLeft: "10px"}} disabled={this.state.selectedUsers.length === 0}>
|
||||||
<Button type="primary" style={{marginLeft: "10px"}}>
|
{i18next.t("general:Sync")}
|
||||||
{i18next.t("general:Sync")}
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
<Button style={{marginLeft: "20px"}}
|
|
||||||
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
|
|
||||||
{i18next.t("general:Edit")} LDAP
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</Popconfirm>
|
||||||
)}
|
<Button style={{marginLeft: "20px"}}
|
||||||
loading={users === null}
|
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
|
||||||
/>
|
{i18next.t("general:Edit")} LDAP
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={users === null}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Row style={{width: "100%", justifyContent: "center"}}>
|
{
|
||||||
<Col span={22}>
|
this.renderTable(this.state.users)
|
||||||
{
|
}
|
||||||
this.renderTable(this.state.users)
|
|
||||||
}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => {
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => {
|
||||||
this.props.history.push(`/organizations/${this.state.organizationName}`);
|
this.props.history.push(`/organizations/${this.state.organizationName}`);
|
||||||
|
220
web/src/MessageEditPage.js
Normal file
220
web/src/MessageEditPage.js
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Button, Card, Col, Input, Row, Select} from "antd";
|
||||||
|
import * as ChatBackend from "./backend/ChatBackend";
|
||||||
|
import * as MessageBackend from "./backend/MessageBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
const {TextArea} = Input;
|
||||||
|
|
||||||
|
class MessageEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
messageName: props.match.params.messageName,
|
||||||
|
message: null,
|
||||||
|
organizations: [],
|
||||||
|
chats: [],
|
||||||
|
users: [],
|
||||||
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getMessage();
|
||||||
|
this.getOrganizations();
|
||||||
|
this.getChats();
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessage() {
|
||||||
|
MessageBackend.getMessage("admin", this.state.messageName)
|
||||||
|
.then((message) => {
|
||||||
|
this.setState({
|
||||||
|
message: message,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getUsers(message.organization);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getChats() {
|
||||||
|
ChatBackend.getChats("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
chats: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUsers(organizationName) {
|
||||||
|
UserBackend.getUsers(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
users: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseMessageField(key, value) {
|
||||||
|
if ([].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMessageField(key, value) {
|
||||||
|
value = this.parseMessageField(key, value);
|
||||||
|
|
||||||
|
const message = this.state.message;
|
||||||
|
message[key] = value;
|
||||||
|
this.setState({
|
||||||
|
message: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMessage() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("message:New Message") : i18next.t("message:Edit Message")}
|
||||||
|
<Button onClick={() => this.submitMessageEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitMessageEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteMessage()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||||
|
<Row style={{marginTop: "10px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.message.organization} onChange={(value => {this.updateMessageField("organization", value);})}
|
||||||
|
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.message.name} onChange={e => {
|
||||||
|
this.updateMessageField("name", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("message:Chat"), i18next.t("message:Chat - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.message.chat} onChange={(value => {this.updateMessageField("chat", value);})}
|
||||||
|
options={this.state.chats.map((chat) => Setting.getOption(chat.name, chat.name))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("message:Author"), i18next.t("message:Author - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.message.author} onChange={(value => {this.updateMessageField("author", value);})}
|
||||||
|
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))
|
||||||
|
} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("message:Text"), i18next.t("message:Text - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<TextArea rows={10} value={this.state.message.text} onChange={e => {
|
||||||
|
this.updateMessageField("text", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitMessageEdit(willExist) {
|
||||||
|
const message = Setting.deepCopy(this.state.message);
|
||||||
|
MessageBackend.updateMessage(this.state.message.owner, this.state.messageName, message)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
|
this.setState({
|
||||||
|
messageName: this.state.message.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push("/messages");
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/messages/${this.state.message.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
|
this.updateMessageField("name", this.state.messageName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMessage() {
|
||||||
|
MessageBackend.deleteMessage(this.state.message)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push("/messages");
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.message !== null ? this.renderMessage() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
|
<Button size="large" onClick={() => this.submitMessageEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitMessageEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteMessage()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MessageEditPage;
|
236
web/src/MessageListPage.js
Normal file
236
web/src/MessageListPage.js
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Button, Table} from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as MessageBackend from "./backend/MessageBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
|
class MessageListPage extends BaseListPage {
|
||||||
|
newMessage() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "admin", // this.props.account.messagename,
|
||||||
|
name: `message_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
organization: this.props.account.owner,
|
||||||
|
chat: "",
|
||||||
|
author: `${this.props.account.owner}/${this.props.account.name}`,
|
||||||
|
text: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessage() {
|
||||||
|
const newMessage = this.newMessage();
|
||||||
|
MessageBackend.addMessage(newMessage)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.props.history.push({pathname: `/messages/${newMessage.name}`, mode: "add"});
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMessage(i) {
|
||||||
|
MessageBackend.deleteMessage(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(messages) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: "organization",
|
||||||
|
key: "organization",
|
||||||
|
width: "150px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("organization"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/organizations/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
width: "120px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("name"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/messages/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: "createdTime",
|
||||||
|
key: "createdTime",
|
||||||
|
width: "150px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("message:Chat"),
|
||||||
|
dataIndex: "chat",
|
||||||
|
key: "chat",
|
||||||
|
width: "120px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("chat"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/chats/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("message:Author"),
|
||||||
|
dataIndex: "author",
|
||||||
|
key: "author",
|
||||||
|
width: "120px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("author"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/users/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("message:Text"),
|
||||||
|
dataIndex: "text",
|
||||||
|
key: "text",
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("text"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: "",
|
||||||
|
key: "op",
|
||||||
|
width: "170px",
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/messages/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<PopconfirmModal
|
||||||
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteMessage(index)}
|
||||||
|
>
|
||||||
|
</PopconfirmModal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.pagination.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={messages} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Messages")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addMessage.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.category !== undefined && params.category !== null) {
|
||||||
|
field = "category";
|
||||||
|
value = params.category;
|
||||||
|
} else if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
MessageBackend.getMessages("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data,
|
||||||
|
pagination: {
|
||||||
|
...params.pagination,
|
||||||
|
total: res.data2,
|
||||||
|
},
|
||||||
|
searchText: params.searchText,
|
||||||
|
searchedColumn: params.searchedColumn,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (Setting.isResponseDenied(res)) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
isAuthorized: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MessageListPage;
|
@ -21,8 +21,8 @@ import * as Setting from "./Setting";
|
|||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {LinkOutlined} from "@ant-design/icons";
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
import LdapTable from "./LdapTable";
|
import LdapTable from "./table/LdapTable";
|
||||||
import AccountTable from "./AccountTable";
|
import AccountTable from "./table/AccountTable";
|
||||||
import ThemeEditor from "./common/theme/ThemeEditor";
|
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
@ -139,7 +139,7 @@ class PermissionListPage extends BaseListPage {
|
|||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("users"),
|
...this.getColumnSearchProps("users"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return Setting.getTags(text);
|
return Setting.getTags(text, "users");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -150,7 +150,7 @@ class PermissionListPage extends BaseListPage {
|
|||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("roles"),
|
...this.getColumnSearchProps("roles"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return Setting.getTags(text);
|
return Setting.getTags(text, "roles");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -19,12 +19,12 @@ import * as ProviderBackend from "./backend/ProviderBackend";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {authConfig} from "./auth/Auth";
|
import {authConfig} from "./auth/Auth";
|
||||||
import * as ProviderEditTestEmail from "./TestEmailWidget";
|
import * as ProviderEditTestEmail from "./common/TestEmailWidget";
|
||||||
import * as ProviderEditTestSms from "./TestSmsWidget";
|
import * as ProviderEditTestSms from "./common/TestSmsWidget";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import {CaptchaPreview} from "./common/CaptchaPreview";
|
import {CaptchaPreview} from "./common/CaptchaPreview";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import {CountryCodeSelect} from "./common/CountryCodeSelect";
|
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
const {TextArea} = Input;
|
const {TextArea} = Input;
|
||||||
@ -110,7 +110,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
getClientSecretLabel(provider) {
|
getClientSecretLabel(provider) {
|
||||||
switch (provider.category) {
|
switch (provider.category) {
|
||||||
case "Email":
|
case "Email":
|
||||||
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
|
return Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"));
|
||||||
case "SMS":
|
case "SMS":
|
||||||
if (provider.type === "Volc Engine SMS") {
|
if (provider.type === "Volc Engine SMS") {
|
||||||
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:Secret access key - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:Secret access key - Tooltip"));
|
||||||
@ -130,6 +130,24 @@ class ProviderEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getProviderSubTypeOptions(type) {
|
||||||
|
if (type === "WeCom" || type === "Infoflow") {
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
{id: "Internal", name: i18next.t("provider:Internal")},
|
||||||
|
{id: "Third-party", name: i18next.t("provider:Third-party")},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else if (type === "Aliyun Captcha") {
|
||||||
|
return [
|
||||||
|
{id: "nc", name: i18next.t("provider:Sliding Validation")},
|
||||||
|
{id: "ic", name: i18next.t("provider:Intelligent Validation")},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getAppIdRow(provider) {
|
getAppIdRow(provider) {
|
||||||
let text = "";
|
let text = "";
|
||||||
let tooltip = "";
|
let tooltip = "";
|
||||||
@ -315,7 +333,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField("subType", value);
|
this.updateProviderField("subType", value);
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
Setting.getProviderSubTypeOptions(this.state.provider.type).map((providerSubType, index) => <Option key={index} value={providerSubType.id}>{providerSubType.name}</Option>)
|
this.getProviderSubTypeOptions(this.state.provider.type).map((providerSubType, index) => <Option key={index} value={providerSubType.id}>{providerSubType.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
@ -331,7 +349,10 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField("method", value);
|
this.updateProviderField("method", value);
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
[{name: "Normal"}, {name: "Silent"}].map((method, index) => <Option key={index} value={method.name}>{method.name}</Option>)
|
[
|
||||||
|
{id: "Normal", name: i18next.t("provider:Normal")},
|
||||||
|
{id: "Silent", name: i18next.t("provider:Silent")},
|
||||||
|
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
@ -440,13 +461,15 @@ class ProviderEditPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" ? null : (
|
this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{this.state.provider.type === "Aliyun Captcha"
|
{this.state.provider.type === "Aliyun Captcha"
|
||||||
? Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip"))
|
? Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip"))
|
||||||
: Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"))}
|
: this.state.provider.type === "WeChat Pay"
|
||||||
|
? Setting.getLabel("appId", "appId")
|
||||||
|
: Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"))}
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.clientId2} onChange={e => {
|
<Input value={this.state.provider.clientId2} onChange={e => {
|
||||||
@ -454,18 +477,22 @@ class ProviderEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
{
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
this.state.provider.type === "WeChat Pay" ? null : (
|
||||||
{this.state.provider.type === "Aliyun Captcha"
|
<Row style={{marginTop: "20px"}} >
|
||||||
? Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"))
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
: Setting.getLabel(i18next.t("provider:Client secret 2"), i18next.t("provider:Client secret 2 - Tooltip"))}
|
{this.state.provider.type === "Aliyun Captcha"
|
||||||
</Col>
|
? Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"))
|
||||||
<Col span={22} >
|
: Setting.getLabel(i18next.t("provider:Client secret 2"), i18next.t("provider:Client secret 2 - Tooltip"))}
|
||||||
<Input value={this.state.provider.clientSecret2} onChange={e => {
|
</Col>
|
||||||
this.updateProviderField("clientSecret2", e.target.value);
|
<Col span={22} >
|
||||||
}} />
|
<Input value={this.state.provider.clientSecret2} onChange={e => {
|
||||||
</Col>
|
this.updateProviderField("clientSecret2", e.target.value);
|
||||||
</Row>
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -794,6 +821,20 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
this.state.provider.type === "WeChat Pay" ? (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel("cert", "cert")} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.provider.cert} onChange={e => {
|
||||||
|
this.updateProviderField("cert", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
{this.getAppIdRow(this.state.provider)}
|
{this.getAppIdRow(this.state.provider)}
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
@ -130,7 +130,7 @@ class RoleListPage extends BaseListPage {
|
|||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("users"),
|
...this.getColumnSearchProps("users"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return Setting.getTags(text);
|
return Setting.getTags(text, "users");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -141,7 +141,7 @@ class RoleListPage extends BaseListPage {
|
|||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("roles"),
|
...this.getColumnSearchProps("roles"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return Setting.getTags(text);
|
return Setting.getTags(text, "roles");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Checkbox, Form, Modal, Select, Tag, Tooltip, message, theme} from "antd";
|
import {Select, Tag, Tooltip, message, theme} from "antd";
|
||||||
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
||||||
import {isMobile as isMobileDevice} from "react-device-detect";
|
import {isMobile as isMobileDevice} from "react-device-detect";
|
||||||
import "./i18n";
|
import "./i18n";
|
||||||
@ -42,7 +42,7 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
|
|||||||
{label: "日本語", key: "ja", country: "JP", alt: "日本語"},
|
{label: "日本語", key: "ja", country: "JP", alt: "日本語"},
|
||||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||||
{label: "TiếngViệt", key: "vi", country: "VI", alt: "TiếngViệt"},
|
{label: "TiếngViệt", key: "vi", country: "VN", alt: "TiếngViệt"},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getThemeData(organization, application) {
|
export function getThemeData(organization, application) {
|
||||||
@ -552,10 +552,6 @@ export function addRow(array, row, position = "end") {
|
|||||||
return position === "end" ? [...array, row] : [row, ...array];
|
return position === "end" ? [...array, row] : [row, ...array];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prependRow(array, row) {
|
|
||||||
return [row, ...array];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteRow(array, i) {
|
export function deleteRow(array, i) {
|
||||||
// return array = array.slice(0, i).concat(array.slice(i + 1));
|
// return array = array.slice(0, i).concat(array.slice(i + 1));
|
||||||
return [...array.slice(0, i), ...array.slice(i + 1)];
|
return [...array.slice(0, i), ...array.slice(i + 1)];
|
||||||
@ -585,76 +581,6 @@ export function isMobile() {
|
|||||||
return isMobileDevice;
|
return isMobileDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTermsOfUseContent(url, setTermsOfUseContent) {
|
|
||||||
fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
}).then(r => {
|
|
||||||
r.text().then(setTermsOfUseContent);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAgreementRequired(application) {
|
|
||||||
if (application) {
|
|
||||||
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
|
||||||
if (!agreementItem || agreementItem.rule === "None" || !agreementItem.rule) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (agreementItem.required) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isDefaultTrue(application) {
|
|
||||||
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
|
||||||
|
|
||||||
return isAgreementRequired(application) && agreementItem.rule === "Signin (Default True)";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderAgreement(required, onClick, noStyle, layout, initialValue) {
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
name="agreement"
|
|
||||||
key="agreement"
|
|
||||||
valuePropName="checked"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: required,
|
|
||||||
message: i18next.t("signup:Please accept the agreement!"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
{...layout}
|
|
||||||
noStyle={noStyle}
|
|
||||||
initialValue={initialValue}
|
|
||||||
>
|
|
||||||
<Checkbox style={{float: "left"}}>
|
|
||||||
{i18next.t("signup:Accept")}
|
|
||||||
<a onClick={onClick}>
|
|
||||||
{i18next.t("signup:Terms of Use")}
|
|
||||||
</a>
|
|
||||||
</Checkbox>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderModal(isOpen, onOk, onCancel, doc) {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={i18next.t("signup:Terms of Use")}
|
|
||||||
open={isOpen}
|
|
||||||
width={"55vw"}
|
|
||||||
closable={false}
|
|
||||||
okText={i18next.t("signup:Accept")}
|
|
||||||
cancelText={i18next.t("signup:Decline")}
|
|
||||||
onOk={onOk}
|
|
||||||
onCancel={onCancel}
|
|
||||||
>
|
|
||||||
<iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={doc} />
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFormattedDate(date) {
|
export function getFormattedDate(date) {
|
||||||
if (date === undefined) {
|
if (date === undefined) {
|
||||||
return null;
|
return null;
|
||||||
@ -731,14 +657,6 @@ export function getAvatarColor(s) {
|
|||||||
return colorList[hash % 4];
|
return colorList[hash % 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLogo(theme) {
|
|
||||||
if (theme === "Dark") {
|
|
||||||
return `${StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`;
|
|
||||||
} else {
|
|
||||||
return `${StaticBaseUrl}/img/casdoor-logo_1185x256.png`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLanguageText(text) {
|
export function getLanguageText(text) {
|
||||||
if (!text.includes("|")) {
|
if (!text.includes("|")) {
|
||||||
return text;
|
return text;
|
||||||
@ -763,11 +681,6 @@ export function setLanguage(language) {
|
|||||||
i18next.changeLanguage(language);
|
i18next.changeLanguage(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setTheme(themeKey) {
|
|
||||||
localStorage.setItem("theme", themeKey);
|
|
||||||
dispatchEvent(new Event("changeTheme"));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAcceptLanguage() {
|
export function getAcceptLanguage() {
|
||||||
if (i18next.language === null || i18next.language === "") {
|
if (i18next.language === null || i18next.language === "") {
|
||||||
return "en;q=0.9,en;q=0.8";
|
return "en;q=0.9,en;q=0.8";
|
||||||
@ -948,24 +861,6 @@ export function getProviderTypeOptions(category) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProviderSubTypeOptions(type) {
|
|
||||||
if (type === "WeCom" || type === "Infoflow") {
|
|
||||||
return (
|
|
||||||
[
|
|
||||||
{id: "Internal", name: "Internal"},
|
|
||||||
{id: "Third-party", name: "Third-party"},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} else if (type === "Aliyun Captcha") {
|
|
||||||
return [
|
|
||||||
{id: "nc", name: "Sliding Validation"},
|
|
||||||
{id: "ic", name: "Intelligent Validation"},
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderLogo(application) {
|
export function renderLogo(application) {
|
||||||
if (application === null) {
|
if (application === null) {
|
||||||
return null;
|
return null;
|
||||||
@ -1175,18 +1070,28 @@ export function getTagColor(s) {
|
|||||||
return "processing";
|
return "processing";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTags(tags) {
|
export function getTags(tags, urlPrefix = null) {
|
||||||
const res = [];
|
const res = [];
|
||||||
if (!tags) {
|
if (!tags) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
tags.forEach((tag, i) => {
|
tags.forEach((tag, i) => {
|
||||||
res.push(
|
if (urlPrefix === null) {
|
||||||
<Tag color={getTagColor(tag)}>
|
res.push(
|
||||||
{tag}
|
<Tag color={getTagColor(tag)}>
|
||||||
</Tag>
|
{tag}
|
||||||
);
|
</Tag>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
res.push(
|
||||||
|
<Link to={`/${urlPrefix}/${tag}`}>
|
||||||
|
<Tag color={getTagColor(tag)}>
|
||||||
|
{tag}
|
||||||
|
</Tag>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -1239,94 +1144,3 @@ export function inIframe() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSyncerTableColumns(syncer) {
|
|
||||||
switch (syncer.type) {
|
|
||||||
case "Keycloak":
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"name": "ID",
|
|
||||||
"type": "string",
|
|
||||||
"casdoorName": "Id",
|
|
||||||
"isHashed": true,
|
|
||||||
"values": [
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "USERNAME",
|
|
||||||
"type": "string",
|
|
||||||
"casdoorName": "Name",
|
|
||||||
"isHashed": true,
|
|
||||||
"values": [
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "LAST_NAME+FIRST_NAME",
|
|
||||||
"type": "string",
|
|
||||||
"casdoorName": "DisplayName",
|
|
||||||
"isHashed": true,
|
|
||||||
"values": [
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "EMAIL",
|
|
||||||
"type": "string",
|
|
||||||
"casdoorName": "Email",
|
|
||||||
"isHashed": true,
|
|
||||||
"values": [
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "EMAIL_VERIFIED",
|
|
||||||
"type": "boolean",
|
|
||||||
"casdoorName": "EmailVerified",
|
|
||||||
"isHashed": true,
|
|
||||||
"values": [
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "FIRST_NAME",
|
|
||||||
"type": "string",
|
|
||||||
"casdoorName": "FirstName",
|
|
||||||
"isHashed": true,
|
|
||||||
"values": [
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "LAST_NAME",
|
|
||||||
"type": "string",
|
|
||||||
"casdoorName": "LastName",
|
|
||||||
"isHashed": true,
|
|
||||||
"values": [
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CREATED_TIMESTAMP",
|
|
||||||
"type": "string",
|
|
||||||
"casdoorName": "CreatedTime",
|
|
||||||
"isHashed": true,
|
|
||||||
"values": [
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ENABLED",
|
|
||||||
"type": "boolean",
|
|
||||||
"casdoorName": "IsForbidden",
|
|
||||||
"isHashed": true,
|
|
||||||
"values": [
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -19,7 +19,7 @@ import * as SyncerBackend from "./backend/SyncerBackend";
|
|||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import SyncerTableColumnTable from "./SyncerTableColumnTable";
|
import SyncerTableColumnTable from "./table/SyncerTableColumnTable";
|
||||||
|
|
||||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
@ -80,6 +80,97 @@ class SyncerEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSyncerTableColumns(syncer) {
|
||||||
|
switch (syncer.type) {
|
||||||
|
case "Keycloak":
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"name": "ID",
|
||||||
|
"type": "string",
|
||||||
|
"casdoorName": "Id",
|
||||||
|
"isHashed": true,
|
||||||
|
"values": [
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "USERNAME",
|
||||||
|
"type": "string",
|
||||||
|
"casdoorName": "Name",
|
||||||
|
"isHashed": true,
|
||||||
|
"values": [
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LAST_NAME+FIRST_NAME",
|
||||||
|
"type": "string",
|
||||||
|
"casdoorName": "DisplayName",
|
||||||
|
"isHashed": true,
|
||||||
|
"values": [
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EMAIL",
|
||||||
|
"type": "string",
|
||||||
|
"casdoorName": "Email",
|
||||||
|
"isHashed": true,
|
||||||
|
"values": [
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EMAIL_VERIFIED",
|
||||||
|
"type": "boolean",
|
||||||
|
"casdoorName": "EmailVerified",
|
||||||
|
"isHashed": true,
|
||||||
|
"values": [
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FIRST_NAME",
|
||||||
|
"type": "string",
|
||||||
|
"casdoorName": "FirstName",
|
||||||
|
"isHashed": true,
|
||||||
|
"values": [
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LAST_NAME",
|
||||||
|
"type": "string",
|
||||||
|
"casdoorName": "LastName",
|
||||||
|
"isHashed": true,
|
||||||
|
"values": [
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CREATED_TIMESTAMP",
|
||||||
|
"type": "string",
|
||||||
|
"casdoorName": "CreatedTime",
|
||||||
|
"isHashed": true,
|
||||||
|
"values": [
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ENABLED",
|
||||||
|
"type": "boolean",
|
||||||
|
"casdoorName": "IsForbidden",
|
||||||
|
"isHashed": true,
|
||||||
|
"values": [
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderSyncer() {
|
renderSyncer() {
|
||||||
return (
|
return (
|
||||||
<Card size="small" title={
|
<Card size="small" title={
|
||||||
@ -120,7 +211,7 @@ class SyncerEditPage extends React.Component {
|
|||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.syncer.type} onChange={(value => {
|
<Select virtual={false} style={{width: "100%"}} value={this.state.syncer.type} onChange={(value => {
|
||||||
this.updateSyncerField("type", value);
|
this.updateSyncerField("type", value);
|
||||||
const syncer = this.state.syncer;
|
const syncer = this.state.syncer;
|
||||||
syncer["tableColumns"] = Setting.getSyncerTableColumns(this.state.syncer);
|
syncer["tableColumns"] = this.getSyncerTableColumns(this.state.syncer);
|
||||||
syncer.table = (value === "Keycloak") ? "user_entity" : this.state.syncer.table;
|
syncer.table = (value === "Keycloak") ? "user_entity" : this.state.syncer.table;
|
||||||
this.setState({
|
this.setState({
|
||||||
syncer: syncer,
|
syncer: syncer,
|
||||||
|
@ -45,7 +45,7 @@ class SystemInfo extends React.Component {
|
|||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
Setting.showMessage("error", `System info failed to get: ${error}`);
|
Setting.showMessage("error", `System info failed to get: ${error}`);
|
||||||
});
|
});
|
||||||
}, 1000 * 3);
|
}, 1000 * 2);
|
||||||
this.setState({intervalId: id});
|
this.setState({intervalId: id});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
Setting.showMessage("error", `System info failed to get: ${error}`);
|
Setting.showMessage("error", `System info failed to get: ${error}`);
|
||||||
|
@ -13,23 +13,23 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from "antd";
|
import {Button, Card, Col, Input, InputNumber, Result, Row, Select, Spin, Switch} from "antd";
|
||||||
import * as UserBackend from "./backend/UserBackend";
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import CropperDiv from "./CropperDiv.js";
|
import CropperDivModal from "./common/modal/CropperDivModal.js";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import PasswordModal from "./PasswordModal";
|
import PasswordModal from "./common/modal/PasswordModal";
|
||||||
import ResetModal from "./ResetModal";
|
import ResetModal from "./common/modal/ResetModal";
|
||||||
import AffiliationSelect from "./common/AffiliationSelect";
|
import AffiliationSelect from "./common/select/AffiliationSelect";
|
||||||
import OAuthWidget from "./common/OAuthWidget";
|
import OAuthWidget from "./common/OAuthWidget";
|
||||||
import SamlWidget from "./common/SamlWidget";
|
import SamlWidget from "./common/SamlWidget";
|
||||||
import SelectRegionBox from "./SelectRegionBox";
|
import RegionSelect from "./common/select/RegionSelect";
|
||||||
import WebAuthnCredentialTable from "./WebauthnCredentialTable";
|
import WebAuthnCredentialTable from "./table/WebauthnCredentialTable";
|
||||||
import ManagedAccountTable from "./ManagedAccountTable";
|
import ManagedAccountTable from "./table/ManagedAccountTable";
|
||||||
import PropertyTable from "./propertyTable";
|
import PropertyTable from "./table/propertyTable";
|
||||||
import {CountryCodeSelect} from "./common/CountryCodeSelect";
|
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -110,9 +110,9 @@ class UserEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseUserField(key, value) {
|
parseUserField(key, value) {
|
||||||
// if ([].includes(key)) {
|
if (["score", "karma", "ranking"].includes(key)) {
|
||||||
// value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
// }
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ class UserEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}}>
|
<Row style={{marginTop: "20px"}}>
|
||||||
<CropperDiv buttonText={`${i18next.t("user:Upload a photo")}...`} title={i18next.t("user:Upload a photo")} user={this.state.user} organization={this.state.organizations.find(organization => organization.name === this.state.organizationName)} />
|
<CropperDivModal buttonText={`${i18next.t("user:Upload a photo")}...`} title={i18next.t("user:Upload a photo")} user={this.state.user} organization={this.state.organizations.find(organization => organization.name === this.state.organizationName)} />
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -341,7 +341,7 @@ class UserEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<SelectRegionBox defaultValue={this.state.user.region} onChange={(value) => {
|
<RegionSelect defaultValue={this.state.user.region} onChange={(value) => {
|
||||||
this.updateUserField("region", value);
|
this.updateUserField("region", value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@ -360,6 +360,19 @@ class UserEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
} else if (accountItem.name === "Address") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Address"), i18next.t("user:Address - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.user.address} onChange={e => {
|
||||||
|
this.updateUserField("address", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
} else if (accountItem.name === "Affiliation") {
|
} else if (accountItem.name === "Affiliation") {
|
||||||
return (
|
return (
|
||||||
(this.state.application === null || this.state.user === null) ? null : (
|
(this.state.application === null || this.state.user === null) ? null : (
|
||||||
@ -379,6 +392,32 @@ class UserEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
} else if (accountItem.name === "ID card type") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:ID card type"), i18next.t("user:ID card type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.user.idCardType} onChange={e => {
|
||||||
|
this.updateUserField("idCardType", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
} else if (accountItem.name === "ID card") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:ID card"), i18next.t("user:ID card - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.user.idCard} onChange={e => {
|
||||||
|
this.updateUserField("idCard", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
} else if (accountItem.name === "Homepage") {
|
} else if (accountItem.name === "Homepage") {
|
||||||
return (
|
return (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
@ -431,6 +470,97 @@ class UserEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
} else if (accountItem.name === "Language") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Language"), i18next.t("user:Language - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.user.language} onChange={e => {
|
||||||
|
this.updateUserField("language", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
} else if (accountItem.name === "Gender") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Gender"), i18next.t("user:Gender - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.user.gender} onChange={e => {
|
||||||
|
this.updateUserField("gender", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
} else if (accountItem.name === "Birthday") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Birthday"), i18next.t("user:Birthday - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.user.birthday} onChange={e => {
|
||||||
|
this.updateUserField("birthday", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
} else if (accountItem.name === "Education") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Education"), i18next.t("user:Education - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.user.education} onChange={e => {
|
||||||
|
this.updateUserField("education", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
} else if (accountItem.name === "Score") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Score"), i18next.t("user:Score - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.user.score} onChange={value => {
|
||||||
|
this.updateUserField("score", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
} else if (accountItem.name === "Karma") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Karma"), i18next.t("user:Karma - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.user.karma} onChange={value => {
|
||||||
|
this.updateUserField("karma", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
} else if (accountItem.name === "Ranking") {
|
||||||
|
return (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("user:Ranking"), i18next.t("user:Ranking - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.user.ranking} onChange={value => {
|
||||||
|
this.updateUserField("ranking", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
} else if (accountItem.name === "Signup application") {
|
} else if (accountItem.name === "Signup application") {
|
||||||
return (
|
return (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
@ -614,7 +744,7 @@ class UserEditPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitUserEdit(willExist) {
|
submitUserEdit(needExit) {
|
||||||
const user = Setting.deepCopy(this.state.user);
|
const user = Setting.deepCopy(this.state.user);
|
||||||
UserBackend.updateUser(this.state.organizationName, this.state.userName, user)
|
UserBackend.updateUser(this.state.organizationName, this.state.userName, user)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -626,13 +756,18 @@ class UserEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.props.history !== undefined) {
|
if (this.props.history !== undefined) {
|
||||||
if (willExist) {
|
if (needExit) {
|
||||||
this.props.history.push("/users");
|
const userListUrl = sessionStorage.getItem("userListUrl");
|
||||||
|
if (userListUrl !== null) {
|
||||||
|
this.props.history.push(userListUrl);
|
||||||
|
} else {
|
||||||
|
this.props.history.push("/users");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`);
|
this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (willExist) {
|
if (needExit) {
|
||||||
if (this.state.returnUrl) {
|
if (this.state.returnUrl) {
|
||||||
window.location.href = this.state.returnUrl;
|
window.location.href = this.state.returnUrl;
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,7 @@ class UserListPage extends BaseListPage {
|
|||||||
UserBackend.addUser(newUser)
|
UserBackend.addUser(newUser)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
|
sessionStorage.setItem("userListUrl", window.location.pathname);
|
||||||
this.props.history.push({pathname: `/users/${newUser.owner}/${newUser.name}`, mode: "add"});
|
this.props.history.push({pathname: `/users/${newUser.owner}/${newUser.name}`, mode: "add"});
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
} else {
|
} else {
|
||||||
@ -341,7 +342,10 @@ class UserListPage extends BaseListPage {
|
|||||||
const disabled = (record.owner === this.props.account.owner && record.name === this.props.account.name);
|
const disabled = (record.owner === this.props.account.owner && record.name === this.props.account.name);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => {
|
||||||
|
sessionStorage.setItem("userListUrl", window.location.pathname);
|
||||||
|
this.props.history.push(`/users/${record.owner}/${record.name}`);
|
||||||
|
}}>{i18next.t("general:Edit")}</Button>
|
||||||
<PopconfirmModal
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteUser(index)}
|
onConfirm={() => this.deleteUser(index)}
|
||||||
@ -402,6 +406,8 @@ class UserListPage extends BaseListPage {
|
|||||||
const users = res.data;
|
const users = res.data;
|
||||||
if (users.length > 0) {
|
if (users.length > 0) {
|
||||||
this.getOrganization(users[0].owner);
|
this.getOrganization(users[0].owner);
|
||||||
|
} else {
|
||||||
|
this.getOrganization(this.state.organizationName);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Setting.isResponseDenied(res)) {
|
if (Setting.isResponseDenied(res)) {
|
||||||
@ -430,6 +436,8 @@ class UserListPage extends BaseListPage {
|
|||||||
const users = res.data;
|
const users = res.data;
|
||||||
if (users.length > 0) {
|
if (users.length > 0) {
|
||||||
this.getOrganization(users[0].owner);
|
this.getOrganization(users[0].owner);
|
||||||
|
} else {
|
||||||
|
this.getOrganization(this.state.organizationName);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Setting.isResponseDenied(res)) {
|
if (Setting.isResponseDenied(res)) {
|
||||||
|
@ -19,7 +19,7 @@ import * as WebhookBackend from "./backend/WebhookBackend";
|
|||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import WebhookHeaderTable from "./WebhookHeaderTable";
|
import WebhookHeaderTable from "./table/WebhookHeaderTable";
|
||||||
|
|
||||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
|
@ -22,7 +22,7 @@ import i18next from "i18next";
|
|||||||
import {SendCodeInput} from "../common/SendCodeInput";
|
import {SendCodeInput} from "../common/SendCodeInput";
|
||||||
import * as UserBackend from "../backend/UserBackend";
|
import * as UserBackend from "../backend/UserBackend";
|
||||||
import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons";
|
import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons";
|
||||||
import CustomGithubCorner from "../CustomGithubCorner";
|
import CustomGithubCorner from "../common/CustomGithubCorner";
|
||||||
import {withRouter} from "react-router-dom";
|
import {withRouter} from "react-router-dom";
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -33,9 +33,8 @@ class ForgetPage extends React.Component {
|
|||||||
classes: props,
|
classes: props,
|
||||||
applicationName: props.applicationName ?? props.match.params?.applicationName,
|
applicationName: props.applicationName ?? props.match.params?.applicationName,
|
||||||
msg: null,
|
msg: null,
|
||||||
userId: "",
|
|
||||||
username: "",
|
|
||||||
name: "",
|
name: "",
|
||||||
|
username: "",
|
||||||
phone: "",
|
phone: "",
|
||||||
email: "",
|
email: "",
|
||||||
dest: "",
|
dest: "",
|
||||||
@ -86,7 +85,7 @@ class ForgetPage extends React.Component {
|
|||||||
const phone = res.data.phone;
|
const phone = res.data.phone;
|
||||||
const email = res.data.email;
|
const email = res.data.email;
|
||||||
|
|
||||||
if (phone === "" && email === "") {
|
if (!phone && !email) {
|
||||||
Setting.showMessage("error", "no verification method!");
|
Setting.showMessage("error", "no verification method!");
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -124,18 +123,16 @@ class ForgetPage extends React.Component {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "step2":
|
case "step2":
|
||||||
const oAuthParams = Util.getOAuthGetParameters();
|
UserBackend.verifyCode({
|
||||||
|
|
||||||
AuthBackend.login({
|
|
||||||
application: forms.step2.getFieldValue("application"),
|
application: forms.step2.getFieldValue("application"),
|
||||||
organization: forms.step2.getFieldValue("organization"),
|
organization: forms.step2.getFieldValue("organization"),
|
||||||
username: forms.step2.getFieldValue("dest"),
|
username: forms.step2.getFieldValue("dest"),
|
||||||
name: this.state.name,
|
name: this.state.name,
|
||||||
code: forms.step2.getFieldValue("code"),
|
code: forms.step2.getFieldValue("code"),
|
||||||
type: "login",
|
type: "login",
|
||||||
}, oAuthParams).then(res => {
|
}).then(res => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({current: 2, userId: res.data});
|
this.setState({current: 2, code: forms.step2.getFieldValue("code")});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", res.msg);
|
Setting.showMessage("error", res.msg);
|
||||||
}
|
}
|
||||||
@ -150,7 +147,7 @@ class ForgetPage extends React.Component {
|
|||||||
onFinish(values) {
|
onFinish(values) {
|
||||||
values.username = this.state.name;
|
values.username = this.state.name;
|
||||||
values.userOwner = this.getApplicationObj()?.organizationObj.name;
|
values.userOwner = this.getApplicationObj()?.organizationObj.name;
|
||||||
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
|
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword, this.state.code).then(res => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
|
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
|
||||||
} else {
|
} else {
|
||||||
@ -387,7 +384,6 @@ class ForgetPage extends React.Component {
|
|||||||
hasFeedback
|
hasFeedback
|
||||||
>
|
>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
disabled={this.state.userId === ""}
|
|
||||||
prefix={<LockOutlined />}
|
prefix={<LockOutlined />}
|
||||||
placeholder={i18next.t("general:Password")}
|
placeholder={i18next.t("general:Password")}
|
||||||
/>
|
/>
|
||||||
@ -414,14 +410,13 @@ class ForgetPage extends React.Component {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
disabled={this.state.userId === ""}
|
|
||||||
prefix={<CheckCircleOutlined />}
|
prefix={<CheckCircleOutlined />}
|
||||||
placeholder={i18next.t("signup:Confirm")}
|
placeholder={i18next.t("signup:Confirm")}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<br />
|
<br />
|
||||||
<Form.Item hidden={this.state.current !== 2}>
|
<Form.Item hidden={this.state.current !== 2}>
|
||||||
<Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
|
<Button block type="primary" htmlType="submit">
|
||||||
{i18next.t("forget:Change Password")}
|
{i18next.t("forget:Change Password")}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -14,11 +14,10 @@
|
|||||||
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {createButton} from "react-social-login-buttons";
|
import {createButton} from "react-social-login-buttons";
|
||||||
import {StaticBaseUrl} from "../Setting";
|
|
||||||
|
|
||||||
function LoginButton({type, align = "center", style = {background: "#ffffff", color: "#000000"}, activeStyle = {background: "#ededee"}}) {
|
function LoginButton({type, logoUrl, align = "center", style = {background: "#ffffff", color: "#000000"}, activeStyle = {background: "#ededee"}}) {
|
||||||
function Icon({width = 24, height = 24, color}) {
|
function Icon({width = 24, height = 24, color}) {
|
||||||
return <img src={`${StaticBaseUrl}/buttons/${type.toLowerCase()}.svg`} alt={`Sign in with ${type}`} style={{width: width, height: height}} />;
|
return <img src={logoUrl} alt={`Sign in with ${type}`} style={{width: width, height: height}} />;
|
||||||
}
|
}
|
||||||
const config = {
|
const config = {
|
||||||
text: `Sign in with ${type}`,
|
text: `Sign in with ${type}`,
|
||||||
|
@ -24,12 +24,13 @@ import * as Provider from "./Provider";
|
|||||||
import * as ProviderButton from "./ProviderButton";
|
import * as ProviderButton from "./ProviderButton";
|
||||||
import * as Util from "./Util";
|
import * as Util from "./Util";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
|
import * as AgreementModal from "../common/modal/AgreementModal";
|
||||||
import SelfLoginButton from "./SelfLoginButton";
|
import SelfLoginButton from "./SelfLoginButton";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import CustomGithubCorner from "../CustomGithubCorner";
|
import CustomGithubCorner from "../common/CustomGithubCorner";
|
||||||
import {SendCodeInput} from "../common/SendCodeInput";
|
import {SendCodeInput} from "../common/SendCodeInput";
|
||||||
import SelectLanguageBox from "../SelectLanguageBox";
|
import LanguageSelect from "../common/select/LanguageSelect";
|
||||||
import {CaptchaModal} from "../common/CaptchaModal";
|
import {CaptchaModal} from "../common/modal/CaptchaModal";
|
||||||
import RedirectForm from "../common/RedirectForm";
|
import RedirectForm from "../common/RedirectForm";
|
||||||
|
|
||||||
class LoginPage extends React.Component {
|
class LoginPage extends React.Component {
|
||||||
@ -135,12 +136,6 @@ class LoginPage extends React.Component {
|
|||||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||||
.then((application) => {
|
.then((application) => {
|
||||||
this.onUpdateApplication(application);
|
this.onUpdateApplication(application);
|
||||||
|
|
||||||
if (application !== null && application !== undefined) {
|
|
||||||
Setting.getTermsOfUseContent(application.termsOfUse, res => {
|
|
||||||
this.setState({termsOfUseContent: res});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
OrganizationBackend.getDefaultApplication("admin", this.state.owner)
|
OrganizationBackend.getDefaultApplication("admin", this.state.owner)
|
||||||
@ -151,12 +146,6 @@ class LoginPage extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
applicationName: res.data.name,
|
applicationName: res.data.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (application !== null && application !== undefined) {
|
|
||||||
Setting.getTermsOfUseContent(application.termsOfUse, res => {
|
|
||||||
this.setState({termsOfUseContent: res});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.onUpdateApplication(null);
|
this.onUpdateApplication(null);
|
||||||
Setting.showMessage("error", res.msg);
|
Setting.showMessage("error", res.msg);
|
||||||
@ -470,25 +459,17 @@ class LoginPage extends React.Component {
|
|||||||
this.renderPasswordOrCodeInput()
|
this.renderPasswordOrCodeInput()
|
||||||
}
|
}
|
||||||
</Row>
|
</Row>
|
||||||
<Form.Item>
|
<div style={{display: "inline-flex", justifyContent: "space-between", width: "320px", marginBottom: AgreementModal.isAgreementRequired(application) ? "5px" : "25px"}}>
|
||||||
{
|
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
||||||
Setting.isAgreementRequired(application) ?
|
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
|
||||||
Setting.renderAgreement(true, () => {
|
{i18next.t("login:Auto sign in")}
|
||||||
this.setState({
|
</Checkbox>
|
||||||
isTermsOfUseVisible: true,
|
</Form.Item>
|
||||||
});
|
|
||||||
}, true, {}, Setting.isDefaultTrue(application)) : (
|
|
||||||
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
|
||||||
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
|
|
||||||
{i18next.t("login:Auto sign in")}
|
|
||||||
</Checkbox>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
Setting.renderForgetLink(application, i18next.t("login:Forgot password?"))
|
Setting.renderForgetLink(application, i18next.t("login:Forgot password?"))
|
||||||
}
|
}
|
||||||
</Form.Item>
|
</div>
|
||||||
|
{AgreementModal.isAgreementRequired(application) ? AgreementModal.renderAgreementFormItem(application, true, {}, this) : null}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -806,11 +787,11 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const visibleOAuthProviderItems = application.providers.filter(providerItem => this.isProviderVisible(providerItem));
|
const visibleOAuthProviderItems = application.providers.filter(providerItem => this.isProviderVisible(providerItem));
|
||||||
if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
if (this.props.preview !== "auto" && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
||||||
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
|
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
|
||||||
return (
|
return (
|
||||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
<div style={{display: "flex", justifyContent: "center", alignItems: "center", width: "100%"}}>
|
||||||
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
|
<Spin size="large" tip={i18next.t("login:Signing in...")} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -833,26 +814,13 @@ class LoginPage extends React.Component {
|
|||||||
{
|
{
|
||||||
Setting.renderLogo(application)
|
Setting.renderLogo(application)
|
||||||
}
|
}
|
||||||
<SelectLanguageBox languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
<LanguageSelect languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||||
{
|
{
|
||||||
this.renderSignedInBox()
|
this.renderSignedInBox()
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.renderForm(application)
|
this.renderForm(application)
|
||||||
}
|
}
|
||||||
{
|
|
||||||
Setting.renderModal(this.state.isTermsOfUseVisible, () => {
|
|
||||||
this.form.current.setFieldsValue({agreement: true});
|
|
||||||
this.setState({
|
|
||||||
isTermsOfUseVisible: false,
|
|
||||||
});
|
|
||||||
}, () => {
|
|
||||||
this.form.current.setFieldsValue({agreement: false});
|
|
||||||
this.setState({
|
|
||||||
isTermsOfUseVisible: false,
|
|
||||||
});
|
|
||||||
}, this.state.termsOfUseContent)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,9 +19,9 @@ import * as UserBackend from "../backend/UserBackend";
|
|||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import AffiliationSelect from "../common/AffiliationSelect";
|
import AffiliationSelect from "../common/select/AffiliationSelect";
|
||||||
import OAuthWidget from "../common/OAuthWidget";
|
import OAuthWidget from "../common/OAuthWidget";
|
||||||
import SelectRegionBox from "../SelectRegionBox";
|
import RegionSelect from "../common/select/RegionSelect";
|
||||||
import {withRouter} from "react-router-dom";
|
import {withRouter} from "react-router-dom";
|
||||||
|
|
||||||
class PromptPage extends React.Component {
|
class PromptPage extends React.Component {
|
||||||
@ -151,7 +151,7 @@ class PromptPage extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col >
|
<Col >
|
||||||
<SelectRegionBox defaultValue={this.state.user.region} onChange={(value) => {
|
<RegionSelect defaultValue={this.state.user.region} onChange={(value) => {
|
||||||
this.updateUserFieldWithoutSubmit("region", value);
|
this.updateUserFieldWithoutSubmit("region", value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -44,62 +44,62 @@ import * as AuthBackend from "./AuthBackend";
|
|||||||
import {getEvent} from "./Util";
|
import {getEvent} from "./Util";
|
||||||
import {Modal} from "antd";
|
import {Modal} from "antd";
|
||||||
|
|
||||||
function getSigninButton(type) {
|
function getSigninButton(provider) {
|
||||||
const text = i18next.t("login:Sign in with {type}").replace("{type}", type);
|
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.type);
|
||||||
if (type === "GitHub") {
|
if (provider.type === "GitHub") {
|
||||||
return <GithubLoginButton text={text} align={"center"} />;
|
return <GithubLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Google") {
|
} else if (provider.type === "Google") {
|
||||||
return <GoogleLoginButton text={text} align={"center"} />;
|
return <GoogleLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "QQ") {
|
} else if (provider.type === "QQ") {
|
||||||
return <QqLoginButton text={text} align={"center"} />;
|
return <QqLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Facebook") {
|
} else if (provider.type === "Facebook") {
|
||||||
return <FacebookLoginButton text={text} align={"center"} />;
|
return <FacebookLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Weibo") {
|
} else if (provider.type === "Weibo") {
|
||||||
return <WeiboLoginButton text={text} align={"center"} />;
|
return <WeiboLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Gitee") {
|
} else if (provider.type === "Gitee") {
|
||||||
return <GiteeLoginButton text={text} align={"center"} />;
|
return <GiteeLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "WeChat") {
|
} else if (provider.type === "WeChat") {
|
||||||
return <WechatLoginButton text={text} align={"center"} />;
|
return <WechatLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "DingTalk") {
|
} else if (provider.type === "DingTalk") {
|
||||||
return <DingTalkLoginButton text={text} align={"center"} />;
|
return <DingTalkLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "LinkedIn") {
|
} else if (provider.type === "LinkedIn") {
|
||||||
return <LinkedInLoginButton text={text} align={"center"} />;
|
return <LinkedInLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "WeCom") {
|
} else if (provider.type === "WeCom") {
|
||||||
return <WeComLoginButton text={text} align={"center"} />;
|
return <WeComLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Lark") {
|
} else if (provider.type === "Lark") {
|
||||||
return <LarkLoginButton text={text} align={"center"} />;
|
return <LarkLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "GitLab") {
|
} else if (provider.type === "GitLab") {
|
||||||
return <GitLabLoginButton text={text} align={"center"} />;
|
return <GitLabLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Adfs") {
|
} else if (provider.type === "Adfs") {
|
||||||
return <AdfsLoginButton text={text} align={"center"} />;
|
return <AdfsLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Casdoor") {
|
} else if (provider.type === "Casdoor") {
|
||||||
return <CasdoorLoginButton text={text} align={"center"} />;
|
return <CasdoorLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Baidu") {
|
} else if (provider.type === "Baidu") {
|
||||||
return <BaiduLoginButton text={text} align={"center"} />;
|
return <BaiduLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Alipay") {
|
} else if (provider.type === "Alipay") {
|
||||||
return <AlipayLoginButton text={text} align={"center"} />;
|
return <AlipayLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Infoflow") {
|
} else if (provider.type === "Infoflow") {
|
||||||
return <InfoflowLoginButton text={text} align={"center"} />;
|
return <InfoflowLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Apple") {
|
} else if (provider.type === "Apple") {
|
||||||
return <AppleLoginButton text={text} align={"center"} />;
|
return <AppleLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "AzureAD") {
|
} else if (provider.type === "AzureAD") {
|
||||||
return <AzureADLoginButton text={text} align={"center"} />;
|
return <AzureADLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Slack") {
|
} else if (provider.type === "Slack") {
|
||||||
return <SlackLoginButton text={text} align={"center"} />;
|
return <SlackLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Steam") {
|
} else if (provider.type === "Steam") {
|
||||||
return <SteamLoginButton text={text} align={"center"} />;
|
return <SteamLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Bilibili") {
|
} else if (provider.type === "Bilibili") {
|
||||||
return <BilibiliLoginButton text={text} align={"center"} />;
|
return <BilibiliLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Okta") {
|
} else if (provider.type === "Okta") {
|
||||||
return <OktaLoginButton text={text} align={"center"} />;
|
return <OktaLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Douyin") {
|
} else if (provider.type === "Douyin") {
|
||||||
return <DouyinLoginButton text={text} align={"center"} />;
|
return <DouyinLoginButton text={text} align={"center"} />;
|
||||||
} else {
|
} else {
|
||||||
return <LoginButton key={type} type={type} />;
|
return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSamlUrl(provider, location) {
|
function goToSamlUrl(provider, location) {
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const clientId = params.get("client_id") ?? "";
|
const clientId = params.get("client_id") ?? "";
|
||||||
const state = params.get("state");
|
const state = params.get("state");
|
||||||
@ -149,12 +149,11 @@ export function renderProviderLogo(provider, application, width, margin, size, l
|
|||||||
}
|
}
|
||||||
} else if (provider.category === "SAML") {
|
} else if (provider.category === "SAML") {
|
||||||
return (
|
return (
|
||||||
<a key={provider.displayName} onClick={() => getSamlUrl(provider, location)}>
|
<a key={provider.displayName} onClick={() => goToSamlUrl(provider, location)}>
|
||||||
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
|
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (provider.type === "Custom") {
|
} else if (provider.type === "Custom") {
|
||||||
// style definition
|
// style definition
|
||||||
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.displayName);
|
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.displayName);
|
||||||
@ -173,7 +172,7 @@ export function renderProviderLogo(provider, application, width, margin, size, l
|
|||||||
);
|
);
|
||||||
} else if (provider.category === "SAML") {
|
} else if (provider.category === "SAML") {
|
||||||
return (
|
return (
|
||||||
<a key={provider.displayName} onClick={() => getSamlUrl(provider, location)} style={customAStyle}>
|
<a key={provider.displayName} onClick={() => goToSamlUrl(provider, location)} style={customAStyle}>
|
||||||
<button style={customButtonStyle}>
|
<button style={customButtonStyle}>
|
||||||
<img width={26} src={getProviderLogoURL(provider)} alt={provider.displayName} style={customImgStyle} />
|
<img width={26} src={getProviderLogoURL(provider)} alt={provider.displayName} style={customImgStyle} />
|
||||||
<span style={customSpanStyle}>{text}</span>
|
<span style={customSpanStyle}>{text}</span>
|
||||||
@ -182,14 +181,27 @@ export function renderProviderLogo(provider, application, width, margin, size, l
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
// big button, for disable password signin
|
||||||
<div key={provider.displayName} style={{marginBottom: "10px"}}>
|
if (provider.category === "SAML") {
|
||||||
<a href={Provider.getAuthUrl(application, provider, "signup")}>
|
return (
|
||||||
{
|
<div key={provider.displayName} style={{marginBottom: "10px"}}>
|
||||||
getSigninButton(provider.type)
|
<a onClick={() => goToSamlUrl(provider, location)}>
|
||||||
}
|
{
|
||||||
</a>
|
getSigninButton(provider)
|
||||||
</div>
|
}
|
||||||
);
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div key={provider.displayName} style={{marginBottom: "10px"}}>
|
||||||
|
<a href={Provider.getAuthUrl(application, provider, "signup")}>
|
||||||
|
{
|
||||||
|
getSigninButton(provider)
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ class ResultPage extends React.Component {
|
|||||||
if (linkInStorage !== null && linkInStorage !== "") {
|
if (linkInStorage !== null && linkInStorage !== "") {
|
||||||
Setting.goToLink(linkInStorage);
|
Setting.goToLink(linkInStorage);
|
||||||
} else {
|
} else {
|
||||||
Setting.redirectToLoginPage(application);
|
Setting.redirectToLoginPage(application, this.props.history);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
{i18next.t("login:Sign In")}
|
{i18next.t("login:Sign In")}
|
||||||
|
@ -21,12 +21,13 @@ import i18next from "i18next";
|
|||||||
import * as Util from "./Util";
|
import * as Util from "./Util";
|
||||||
import {authConfig} from "./Auth";
|
import {authConfig} from "./Auth";
|
||||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||||
|
import * as AgreementModal from "../common/modal/AgreementModal";
|
||||||
import {SendCodeInput} from "../common/SendCodeInput";
|
import {SendCodeInput} from "../common/SendCodeInput";
|
||||||
import SelectRegionBox from "../SelectRegionBox";
|
import RegionSelect from "../common/select/RegionSelect";
|
||||||
import CustomGithubCorner from "../CustomGithubCorner";
|
import CustomGithubCorner from "../common/CustomGithubCorner";
|
||||||
import SelectLanguageBox from "../SelectLanguageBox";
|
import LanguageSelect from "../common/select/LanguageSelect";
|
||||||
import {withRouter} from "react-router-dom";
|
import {withRouter} from "react-router-dom";
|
||||||
import {CountryCodeSelect} from "../common/CountryCodeSelect";
|
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
|
||||||
|
|
||||||
const formItemLayout = {
|
const formItemLayout = {
|
||||||
labelCol: {
|
labelCol: {
|
||||||
@ -47,7 +48,7 @@ const formItemLayout = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const tailFormItemLayout = {
|
export const tailFormItemLayout = {
|
||||||
wrapperCol: {
|
wrapperCol: {
|
||||||
xs: {
|
xs: {
|
||||||
span: 24,
|
span: 24,
|
||||||
@ -105,12 +106,6 @@ class SignupPage extends React.Component {
|
|||||||
ApplicationBackend.getApplication("admin", applicationName)
|
ApplicationBackend.getApplication("admin", applicationName)
|
||||||
.then((application) => {
|
.then((application) => {
|
||||||
this.onUpdateApplication(application);
|
this.onUpdateApplication(application);
|
||||||
|
|
||||||
if (application !== null && application !== undefined) {
|
|
||||||
Setting.getTermsOfUseContent(application.termsOfUse, res => {
|
|
||||||
this.setState({termsOfUseContent: res});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,7 +306,7 @@ class SignupPage extends React.Component {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SelectRegionBox onChange={(value) => {this.setState({region: value});}} />
|
<RegionSelect onChange={(value) => {this.setState({region: value});}} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
} else if (signupItem.name === "Email") {
|
} else if (signupItem.name === "Email") {
|
||||||
@ -391,11 +386,11 @@ class SignupPage extends React.Component {
|
|||||||
},
|
},
|
||||||
({getFieldValue}) => ({
|
({getFieldValue}) => ({
|
||||||
validator: (_, value) => {
|
validator: (_, value) => {
|
||||||
if (!required && value === "") {
|
if (!required && !value) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value !== "" && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
|
if (value && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
|
||||||
this.setState({validPhone: false});
|
this.setState({validPhone: false});
|
||||||
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
|
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
|
||||||
}
|
}
|
||||||
@ -413,24 +408,27 @@ class SignupPage extends React.Component {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
{
|
||||||
name="phoneCode"
|
signupItem.rule !== "No verification" &&
|
||||||
label={i18next.t("code:Phone code")}
|
<Form.Item
|
||||||
rules={[
|
name="phoneCode"
|
||||||
{
|
label={i18next.t("code:Phone code")}
|
||||||
required: required,
|
rules={[
|
||||||
message: i18next.t("code:Please input your phone verification code!"),
|
{
|
||||||
},
|
required: required,
|
||||||
]}
|
message: i18next.t("code:Please input your phone verification code!"),
|
||||||
>
|
},
|
||||||
<SendCodeInput
|
]}
|
||||||
disabled={!this.state.validPhone}
|
>
|
||||||
method={"signup"}
|
<SendCodeInput
|
||||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
|
disabled={!this.state.validPhone}
|
||||||
application={application}
|
method={"signup"}
|
||||||
countryCode={this.form.current?.getFieldValue("countryCode")}
|
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
|
||||||
/>
|
application={application}
|
||||||
</Form.Item>
|
countryCode={this.form.current?.getFieldValue("countryCode")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
} else if (signupItem.name === "Password") {
|
} else if (signupItem.name === "Password") {
|
||||||
@ -477,32 +475,10 @@ class SignupPage extends React.Component {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
} else if (signupItem.name === "Agreement") {
|
} else if (signupItem.name === "Agreement") {
|
||||||
return (
|
return AgreementModal.renderAgreementFormItem(application, required, tailFormItemLayout, this);
|
||||||
Setting.renderAgreement(Setting.isAgreementRequired(application), () => {
|
|
||||||
this.setState({
|
|
||||||
isTermsOfUseVisible: true,
|
|
||||||
});
|
|
||||||
}, false, tailFormItemLayout, Setting.isDefaultTrue(application))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderModal() {
|
|
||||||
return (
|
|
||||||
Setting.renderModal(this.state.isTermsOfUseVisible, () => {
|
|
||||||
this.form.current.setFieldsValue({agreement: true});
|
|
||||||
this.setState({
|
|
||||||
isTermsOfUseVisible: false,
|
|
||||||
});
|
|
||||||
}, () => {
|
|
||||||
this.form.current.setFieldsValue({agreement: false});
|
|
||||||
this.setState({
|
|
||||||
isTermsOfUseVisible: false,
|
|
||||||
});
|
|
||||||
}, this.state.termsOfUseContent)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderForm(application) {
|
renderForm(application) {
|
||||||
if (!application.enableSignUp) {
|
if (!application.enableSignUp) {
|
||||||
return (
|
return (
|
||||||
@ -615,16 +591,13 @@ class SignupPage extends React.Component {
|
|||||||
{
|
{
|
||||||
Setting.renderLogo(application)
|
Setting.renderLogo(application)
|
||||||
}
|
}
|
||||||
<SelectLanguageBox languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
<LanguageSelect languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||||
{
|
{
|
||||||
this.renderForm(application)
|
this.renderForm(application)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
this.renderModal()
|
|
||||||
}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,21 +43,20 @@ export function renderMessage(msg) {
|
|||||||
export function renderMessageLarge(ths, msg) {
|
export function renderMessageLarge(ths, msg) {
|
||||||
if (msg !== null) {
|
if (msg !== null) {
|
||||||
return (
|
return (
|
||||||
<div style={{display: "inline"}}>
|
<Result
|
||||||
<Result
|
style={{margin: "0px auto"}}
|
||||||
status="error"
|
status="error"
|
||||||
title="There was a problem signing you in.."
|
title="There was a problem signing you in.."
|
||||||
subTitle={msg}
|
subTitle={msg}
|
||||||
extra={[
|
extra={[
|
||||||
<Button type="primary" key="back" onClick={() => {
|
<Button type="primary" key="back" onClick={() => {
|
||||||
window.history.go(-2);
|
window.history.go(-2);
|
||||||
}}>
|
}}>
|
||||||
Back
|
Back
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
</Result>
|
</Result>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -55,11 +55,10 @@ export function getUserApplication(owner, name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateApplication(owner, name, application) {
|
export function updateApplication(owner, name, application) {
|
||||||
const newApplication = Setting.deepCopy(application);
|
|
||||||
return fetch(`${Setting.ServerUrl}/api/update-application?id=${owner}/${encodeURIComponent(name)}`, {
|
return fetch(`${Setting.ServerUrl}/api/update-application?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify(newApplication),
|
body: JSON.stringify(application),
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Language": Setting.getAcceptLanguage(),
|
"Accept-Language": Setting.getAcceptLanguage(),
|
||||||
},
|
},
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user