mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-17 00:33:50 +08:00
Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
ade64693e4 | |||
5f8924ed4e | |||
1a6d98d029 | |||
447dd1c534 | |||
86b5d72e5d | |||
6bc4e646e5 | |||
0841eb5c30 | |||
4015c221f7 | |||
dcd6328498 | |||
8080927890 | |||
a95c5b05a9 | |||
865a65d399 | |||
e8b9c67671 | |||
e5ff49f7a7 | |||
9f7924a6e0 | |||
377e200837 | |||
93a76de044 | |||
35bef969fd | |||
4dca3bd3f7 | |||
5de417ecf7 | |||
bf24594fb4 | |||
4a87b4790e | |||
fde8c4b5f6 | |||
55a84644e1 | |||
ca87dd7dea | |||
32af4a766e | |||
4d035bf66d | |||
743dcc9725 | |||
d43d7d1ae9 | |||
c906f1e5d2 | |||
37a26e2a91 | |||
e7018e3de4 | |||
3a64e4dcd8 | |||
380cdc5f7e | |||
3602d9b9a7 | |||
8a9cc2eb8f | |||
4f9a13f18a | |||
a4fc04474e | |||
bf5d4eea48 | |||
0e40a1d922 | |||
ab777c1d73 | |||
ca0fa5fc40 | |||
cfbce79e32 | |||
efc07f0919 | |||
a783315fa2 | |||
1d0af9cf7b | |||
4d48517be9 | |||
178cf7945d | |||
ab5af979c8 | |||
e31aaf5657 | |||
eaf5cb66f3 | |||
83a6b757a4 | |||
2a0dcd746f | |||
22f5ad06ec | |||
18aa70dfb2 | |||
697b3e4998 | |||
d48d515c36 | |||
a5d166c35f | |||
4915963c52 | |||
759a1421e5 | |||
c14bf9fdab | |||
e19f07c521 | |||
39ab71c5db | |||
2c97f8a8b7 | |||
21392dcc14 | |||
953d3d5bc5 | |||
ddee97f544 | |||
c58a6d8725 | |||
a5ff9549c1 | |||
fe57dcbff4 |
@ -82,6 +82,14 @@ Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which fo
|
||||
username:password@tcp(database_ip:database_port)/
|
||||
```
|
||||
|
||||
Then create an empty schema (database) named `casdoor` in your relational database. After the program runs for the first time, it will automatically create tables in this schema.
|
||||
|
||||
You can also edit `main.go`, modify `false` to `true`. It will automatically create the schema (database) named `casdoor` in this database.
|
||||
|
||||
```bash
|
||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
||||
```
|
||||
|
||||
#### Run
|
||||
|
||||
Casdoor provides two run modes, the difference is binary size and user prompt.
|
||||
|
@ -15,7 +15,6 @@
|
||||
package authz
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
xormadapter "github.com/casbin/xorm-adapter/v2"
|
||||
@ -28,8 +27,8 @@ var Enforcer *casbin.Enforcer
|
||||
func InitAuthz() {
|
||||
var err error
|
||||
|
||||
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
|
||||
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true)
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -54,7 +53,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
|
||||
(r.urlPath == p.urlPath || p.urlPath == "*") && \
|
||||
(r.objOwner == p.objOwner || p.objOwner == "*") && \
|
||||
(r.objName == p.objName || p.objName == "*") || \
|
||||
(r.urlPath == "/api/update-user" && r.subOwner == r.objOwner && r.subName == r.objName)
|
||||
(r.subOwner == r.objOwner && r.subName == r.objName)
|
||||
`
|
||||
|
||||
m, err := model.NewModelFromString(modelText)
|
||||
@ -83,14 +82,15 @@ p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
||||
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
||||
p, *, *, GET, /api/login/oauth/logout, *, *
|
||||
p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-users, *, *
|
||||
p, *, *, GET, /api/get-user, *, *
|
||||
p, *, *, GET, /api/get-organizations, *, *
|
||||
p, *, *, GET, /api/get-user-application, *, *
|
||||
p, *, *, GET, /api/get-default-providers, *, *
|
||||
p, *, *, GET, /api/get-resources, *, *
|
||||
p, *, *, POST, /api/upload-avatar, *, *
|
||||
p, *, *, GET, /api/get-product, *, *
|
||||
p, *, *, POST, /api/buy-product, *, *
|
||||
p, *, *, GET, /api/get-payment, *, *
|
||||
p, *, *, GET, /api/get-providers, *, *
|
||||
p, *, *, POST, /api/unlink, *, *
|
||||
p, *, *, POST, /api/set-password, *, *
|
||||
p, *, *, POST, /api/send-verification-code, *, *
|
||||
|
@ -12,7 +12,7 @@ redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
httpProxy = "127.0.0.1:10808"
|
||||
sock5Proxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
logPostOnly = true
|
||||
|
37
conf/conf.go
37
conf/conf.go
@ -15,14 +15,49 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
func GetConfigString(key string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return beego.AppConfig.String(key)
|
||||
}
|
||||
|
||||
func GetConfigBool(key string) (bool, error) {
|
||||
value := GetConfigString(key)
|
||||
if value == "true" {
|
||||
return true, nil
|
||||
} else if value == "false" {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("value %s cannot be converted into bool", value)
|
||||
}
|
||||
|
||||
func GetConfigInt64(key string) (int64, error) {
|
||||
value := GetConfigString(key)
|
||||
num, err := strconv.ParseInt(value, 10, 64)
|
||||
return num, err
|
||||
}
|
||||
|
||||
func init() {
|
||||
//this array contains the beego configuration items that may be modified via env
|
||||
var presetConfigItems = []string{"httpport", "appname"}
|
||||
for _, key := range presetConfigItems {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
beego.AppConfig.Set(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetBeegoConfDataSourceName() string {
|
||||
dataSourceName := beego.AppConfig.String("dataSourceName")
|
||||
dataSourceName := GetConfigString("dataSourceName")
|
||||
|
||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||
if runningInDocker == "true" {
|
||||
|
98
conf/conf_test.go
Normal file
98
conf/conf_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
// 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 conf
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetConfString(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"Should be return casbin", "appname", "casbin"},
|
||||
{"Should be return 8000", "httpport", "8000"},
|
||||
{"Should be return value", "key", "value"},
|
||||
}
|
||||
|
||||
//do some set up job
|
||||
|
||||
os.Setenv("appname", "casbin")
|
||||
os.Setenv("key", "value")
|
||||
|
||||
err := beego.LoadAppConfig("ini", "app.conf")
|
||||
assert.Nil(t, err)
|
||||
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual := GetConfigString(scenery.input)
|
||||
assert.Equal(t, scenery.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfInt(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"Should be return 8000", "httpport", 8001},
|
||||
{"Should be return 8000", "verificationCodeTimeout", 10},
|
||||
}
|
||||
|
||||
//do some set up job
|
||||
os.Setenv("httpport", "8001")
|
||||
|
||||
err := beego.LoadAppConfig("ini", "app.conf")
|
||||
assert.Nil(t, err)
|
||||
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual, err := GetConfigInt64(scenery.input)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, scenery.expected, int(actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfBool(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
description string
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"Should be return false", "SessionOn", false},
|
||||
{"Should be return false", "copyrequestbody", true},
|
||||
}
|
||||
|
||||
//do some set up job
|
||||
os.Setenv("SessionOn", "false")
|
||||
|
||||
err := beego.LoadAppConfig("ini", "app.conf")
|
||||
assert.Nil(t, err)
|
||||
for _, scenery := range scenarios {
|
||||
t.Run(scenery.description, func(t *testing.T) {
|
||||
actual, err := GetConfigBool(scenery.input)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, scenery.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
@ -18,14 +18,17 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
ResponseTypeLogin = "login"
|
||||
ResponseTypeCode = "code"
|
||||
ResponseTypeLogin = "login"
|
||||
ResponseTypeCode = "code"
|
||||
ResponseTypeToken = "token"
|
||||
ResponseTypeIdToken = "id_token"
|
||||
)
|
||||
|
||||
type RequestForm struct {
|
||||
@ -35,6 +38,8 @@ type RequestForm struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
@ -102,7 +107,7 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
|
||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.Email, form.Phone, form.Affiliation)
|
||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.Affiliation)
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
@ -126,8 +131,6 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
}
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
|
||||
|
||||
id := util.GenerateId()
|
||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||
lastUser := object.GetLastUser(form.Organization)
|
||||
@ -167,6 +170,22 @@ func (c *ApiController) Signup() {
|
||||
IsDeleted: false,
|
||||
SignupApplication: application.Name,
|
||||
Properties: map[string]string{},
|
||||
Karma: 0,
|
||||
}
|
||||
|
||||
if len(organization.Tags) > 0 {
|
||||
tokens := strings.Split(organization.Tags[0], "|")
|
||||
if len(tokens) > 0 {
|
||||
user.Tag = tokens[0]
|
||||
}
|
||||
}
|
||||
|
||||
if application.GetSignupItemRule("Display name") == "First, last" {
|
||||
if form.FirstName != "" || form.LastName != "" {
|
||||
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
|
||||
user.FirstName = form.FirstName
|
||||
user.LastName = form.LastName
|
||||
}
|
||||
}
|
||||
|
||||
affected := object.AddUser(user)
|
||||
@ -185,6 +204,12 @@ func (c *ApiController) Signup() {
|
||||
object.DisableVerificationCode(form.Email)
|
||||
object.DisableVerificationCode(checkPhone)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
go object.AddRecord(record)
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||
|
||||
c.ResponseOk(userId)
|
||||
@ -200,10 +225,15 @@ func (c *ApiController) Logout() {
|
||||
user := c.GetSessionUsername()
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
application := c.GetSessionApplication()
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
|
||||
c.ResponseOk(user)
|
||||
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
|
||||
c.ResponseOk(user)
|
||||
return
|
||||
}
|
||||
c.ResponseOk(user, application.HomepageUrl)
|
||||
}
|
||||
|
||||
// GetAccount
|
||||
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -85,7 +86,7 @@ func (c *ApiController) GetUserApplication() {
|
||||
id := c.Input().Get("id")
|
||||
user := object.GetUser(id)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", id))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
@ -38,6 +38,14 @@ func codeToResponse(code *object.Code) *Response {
|
||||
return &Response{Status: "ok", Msg: "", Data: code.Code}
|
||||
}
|
||||
|
||||
func tokenToResponse(token *object.Token) *Response {
|
||||
if token.AccessToken == "" {
|
||||
return &Response{Status: "error", Msg: "fail to get accessToken", Data: token.AccessToken}
|
||||
}
|
||||
return &Response{Status: "ok", Msg: "", Data: token.AccessToken}
|
||||
|
||||
}
|
||||
|
||||
// HandleLoggedIn ...
|
||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
|
||||
userId := user.GetId()
|
||||
@ -66,6 +74,15 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(userId)
|
||||
}
|
||||
} else if form.Type == ResponseTypeToken || form.Type == ResponseTypeIdToken { //implicit flow
|
||||
if !object.IsGrantTypeValid(form.Type, application.GrantTypes) {
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
|
||||
} else {
|
||||
scope := c.Input().Get("scope")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
|
||||
resp = tokenToResponse(token)
|
||||
}
|
||||
|
||||
} else {
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
|
||||
}
|
||||
@ -101,6 +118,7 @@ func (c *ApiController) GetApplicationLogin() {
|
||||
state := c.Input().Get("state")
|
||||
|
||||
msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
||||
application = object.GetMaskedApplication(application, "")
|
||||
if msg != "" {
|
||||
c.ResponseError(msg, application)
|
||||
} else {
|
||||
@ -149,9 +167,16 @@ func (c *ApiController) Login() {
|
||||
var verificationCodeType string
|
||||
var checkResult string
|
||||
|
||||
if form.Name != "" {
|
||||
user = object.GetUserByFields(form.Organization, form.Name)
|
||||
}
|
||||
|
||||
// check result through Email or Phone
|
||||
if strings.Contains(form.Username, "@") {
|
||||
verificationCodeType = "email"
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
|
||||
form.Username = user.Email
|
||||
}
|
||||
checkResult = object.CheckVerificationCode(form.Username, form.Code)
|
||||
} else {
|
||||
verificationCodeType = "phone"
|
||||
@ -160,6 +185,9 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(responseText)
|
||||
return
|
||||
}
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
|
||||
form.Username = user.Phone
|
||||
}
|
||||
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
|
||||
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
|
||||
}
|
||||
@ -174,7 +202,7 @@ func (c *ApiController) Login() {
|
||||
|
||||
user = object.GetUserByFields(form.Organization, form.Username)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@ -186,6 +214,11 @@ func (c *ApiController) Login() {
|
||||
resp = &Response{Status: "error", Msg: msg}
|
||||
} else {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
|
||||
return
|
||||
}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
@ -195,6 +228,11 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
} else if form.Provider != "" {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
|
||||
return
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
|
||||
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
|
||||
providerItem := application.GetProviderItem(provider.Name)
|
||||
@ -229,8 +267,8 @@ func (c *ApiController) Login() {
|
||||
|
||||
setHttpClient(idProvider, provider.Type)
|
||||
|
||||
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
|
||||
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State))
|
||||
if form.State != conf.GetConfigString("authState") && form.State != application.Name {
|
||||
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", conf.GetConfigString("authState"), form.State))
|
||||
return
|
||||
}
|
||||
|
||||
@ -365,6 +403,11 @@ func (c *ApiController) Login() {
|
||||
if c.GetSessionUsername() != "" {
|
||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
|
||||
return
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
} else {
|
||||
|
@ -72,6 +72,15 @@ func (c *ApiController) GetSessionUsername() string {
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionApplication() *object.Application {
|
||||
clientId := c.GetSession("aud")
|
||||
if clientId == nil {
|
||||
return nil
|
||||
}
|
||||
application := object.GetApplicationByClientId(clientId.(string))
|
||||
return application
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||
sessionData := c.GetSessionData()
|
||||
if sessionData != nil &&
|
||||
@ -132,3 +141,11 @@ func wrapActionResponse(affected bool) *Response {
|
||||
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
|
||||
}
|
||||
}
|
||||
|
||||
func wrapErrorResponse(err error) *Response {
|
||||
if err == nil {
|
||||
return &Response{Status: "ok", Msg: ""}
|
||||
} else {
|
||||
return &Response{Status: "error", Msg: err.Error()}
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ func (c *ApiController) UpdateLdap() {
|
||||
}
|
||||
if ldap.AutoSync != 0 {
|
||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0{
|
||||
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
|
||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -48,6 +49,24 @@ func (c *ApiController) GetPayments() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserPayments
|
||||
// @Title GetUserPayments
|
||||
// @Tag Payment API
|
||||
// @Description get payments for a user
|
||||
// @Param owner query string true "The owner of payments"
|
||||
// @Param organization query string true "The organization of the user"
|
||||
// @Param user query string true "The username of the user"
|
||||
// @Success 200 {array} object.Payment The Response object
|
||||
// @router /get-user-payments [get]
|
||||
func (c *ApiController) GetUserPayments() {
|
||||
owner := c.Input().Get("owner")
|
||||
organization := c.Input().Get("organization")
|
||||
user := c.Input().Get("user")
|
||||
|
||||
payments := object.GetUserPayments(owner, organization, user)
|
||||
c.ResponseOk(payments)
|
||||
}
|
||||
|
||||
// @Title GetPayment
|
||||
// @Tag Payment API
|
||||
// @Description get payment
|
||||
@ -114,3 +133,28 @@ func (c *ApiController) DeletePayment() {
|
||||
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title NotifyPayment
|
||||
// @Tag Payment API
|
||||
// @Description notify payment
|
||||
// @Param body body object.Payment true "The details of the payment"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /notify-payment [post]
|
||||
func (c *ApiController) NotifyPayment() {
|
||||
owner := c.Ctx.Input.Param(":owner")
|
||||
providerName := c.Ctx.Input.Param(":provider")
|
||||
productName := c.Ctx.Input.Param(":product")
|
||||
paymentName := c.Ctx.Input.Param(":payment")
|
||||
|
||||
body := c.Ctx.Input.RequestBody
|
||||
|
||||
ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
|
||||
if ok {
|
||||
_, err := c.Ctx.ResponseWriter.Write([]byte("success"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
|
||||
}
|
||||
}
|
||||
|
150
controllers/product.go
Normal file
150
controllers/product.go
Normal file
@ -0,0 +1,150 @@
|
||||
// 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetProducts
|
||||
// @Title GetProducts
|
||||
// @Tag Product API
|
||||
// @Description get products
|
||||
// @Param owner query string true "The owner of products"
|
||||
// @Success 200 {array} object.Product The Response object
|
||||
// @router /get-products [get]
|
||||
func (c *ApiController) GetProducts() {
|
||||
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.GetProducts(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProductCount(owner, field, value)))
|
||||
products := object.GetPaginationProducts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(products, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetProduct
|
||||
// @Tag Product API
|
||||
// @Description get product
|
||||
// @Param id query string true "The id of the product"
|
||||
// @Success 200 {object} object.Product The Response object
|
||||
// @router /get-product [get]
|
||||
func (c *ApiController) GetProduct() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetProduct(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateProduct
|
||||
// @Tag Product API
|
||||
// @Description update product
|
||||
// @Param id query string true "The id of the product"
|
||||
// @Param body body object.Product true "The details of the product"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-product [post]
|
||||
func (c *ApiController) UpdateProduct() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var product object.Product
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateProduct(id, &product))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddProduct
|
||||
// @Tag Product API
|
||||
// @Description add product
|
||||
// @Param body body object.Product true "The details of the product"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-product [post]
|
||||
func (c *ApiController) AddProduct() {
|
||||
var product object.Product
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddProduct(&product))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteProduct
|
||||
// @Tag Product API
|
||||
// @Description delete product
|
||||
// @Param body body object.Product true "The details of the product"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-product [post]
|
||||
func (c *ApiController) DeleteProduct() {
|
||||
var product object.Product
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title BuyProduct
|
||||
// @Tag Product API
|
||||
// @Description buy product
|
||||
// @Param id query string true "The id of the product"
|
||||
// @Param providerName query string true "The name of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /buy-product [post]
|
||||
func (c *ApiController) BuyProduct() {
|
||||
id := c.Input().Get("id")
|
||||
providerName := c.Input().Get("providerName")
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError("Please login first")
|
||||
return
|
||||
}
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||
return
|
||||
}
|
||||
|
||||
payUrl, err := object.BuyProduct(id, providerName, user, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payUrl)
|
||||
}
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -171,23 +172,28 @@ func (c *ApiController) GetOAuthToken() {
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
code := c.Input().Get("code")
|
||||
verifier := c.Input().Get("code_verifier")
|
||||
scope := c.Input().Get("scope")
|
||||
username := c.Input().Get("username")
|
||||
password := c.Input().Get("password")
|
||||
|
||||
if clientId == "" && clientSecret == "" {
|
||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||
}
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier)
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// RefreshToken
|
||||
// @Title RefreshToken
|
||||
// @Tag Token API
|
||||
// @Description refresh OAuth access token
|
||||
// @Param grant_type query string true "OAuth grant type"
|
||||
// @Param refresh_token query string true "OAuth refresh token"
|
||||
// @Param scope query string true "OAuth scope"
|
||||
// @Param client_id query string true "OAuth client id"
|
||||
// @Param client_secret query string true "OAuth client secret"
|
||||
// @Param client_secret query string false "OAuth client secret"
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @router /login/oauth/refresh_token [post]
|
||||
func (c *ApiController) RefreshToken() {
|
||||
@ -201,3 +207,87 @@ func (c *ApiController) RefreshToken() {
|
||||
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// TokenLogout
|
||||
// @Title TokenLogout
|
||||
// @Tag Token API
|
||||
// @Description delete token by AccessToken
|
||||
// @Param id_token_hint query string true "id_token_hint"
|
||||
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
|
||||
// @Param state query string true "state"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /login/oauth/logout [get]
|
||||
func (c *ApiController) TokenLogout() {
|
||||
token := c.Input().Get("id_token_hint")
|
||||
flag, application := object.DeleteTokenByAceessToken(token)
|
||||
redirectUri := c.Input().Get("post_logout_redirect_uri")
|
||||
state := c.Input().Get("state")
|
||||
if application != nil && object.CheckRedirectUriValid(application, redirectUri) {
|
||||
c.Ctx.Redirect(http.StatusFound, redirectUri+"?state="+state)
|
||||
return
|
||||
}
|
||||
c.Data["json"] = wrapActionResponse(flag)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// IntrospectToken
|
||||
// @Title IntrospectToken
|
||||
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a
|
||||
// parameter representing an OAuth 2.0 token and returns a JSON document
|
||||
// representing the meta information surrounding the
|
||||
// token, including whether this token is currently active.
|
||||
// This endpoint only support Basic Authorization.
|
||||
// @Param token formData string true "access_token's value or refresh_token's value"
|
||||
// @Param token_type_hint formData string true "the token type access_token or refresh_token"
|
||||
// @Success 200 {object} object.IntrospectionResponse The Response object
|
||||
// @router /login/oauth/introspect [post]
|
||||
func (c *ApiController) IntrospectToken() {
|
||||
tokenValue := c.Input().Get("token")
|
||||
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
|
||||
if !ok {
|
||||
util.LogWarning(c.Ctx, "Basic Authorization parses failed")
|
||||
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
application := object.GetApplicationByClientId(clientId)
|
||||
if application == nil || application.ClientSecret != clientSecret {
|
||||
util.LogWarning(c.Ctx, "Basic Authorization failed")
|
||||
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
||||
if token == nil {
|
||||
util.LogWarning(c.Ctx, "application: %s can not find token", application.Name)
|
||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
||||
if err != nil || jwtToken.Valid() != nil {
|
||||
// and token revoked case. but we not implement
|
||||
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||
// refs: https://tools.ietf.org/html/rfc7009
|
||||
util.LogWarning(c.Ctx, "token invalid")
|
||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = &object.IntrospectionResponse{
|
||||
Active: true,
|
||||
Scope: jwtToken.Scope,
|
||||
ClientId: clientId,
|
||||
Username: token.User,
|
||||
TokenType: token.TokenType,
|
||||
Exp: jwtToken.ExpiresAt.Unix(),
|
||||
Iat: jwtToken.IssuedAt.Unix(),
|
||||
Nbf: jwtToken.NotBefore.Unix(),
|
||||
Sub: jwtToken.Subject,
|
||||
Aud: jwtToken.Audience,
|
||||
Iss: jwtToken.Issuer,
|
||||
Jti: jwtToken.Id,
|
||||
}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ func (c *ApiController) GetGlobalUsers() {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
|
||||
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
users = object.GetMaskedUsers(users)
|
||||
c.ResponseOk(users, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@ -70,6 +71,7 @@ func (c *ApiController) GetUsers() {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value)))
|
||||
users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
users = object.GetMaskedUsers(users)
|
||||
c.ResponseOk(users, paginator.Nums())
|
||||
}
|
||||
}
|
||||
@ -188,19 +190,23 @@ func (c *ApiController) GetEmailAndPhone() {
|
||||
|
||||
user := object.GetUserByFields(form.Organization, form.Username)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
|
||||
return
|
||||
}
|
||||
|
||||
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name}
|
||||
respUser := object.User{Name: user.Name}
|
||||
var contentType string
|
||||
switch form.Username {
|
||||
case user.Email:
|
||||
contentType = "email"
|
||||
respUser.Email = user.Email
|
||||
case user.Phone:
|
||||
contentType = "phone"
|
||||
respUser.Phone = user.Phone
|
||||
case user.Name:
|
||||
contentType = "username"
|
||||
respUser.Email = util.GetMaskedEmail(user.Email)
|
||||
respUser.Phone = util.GetMaskedPhone(user.Phone)
|
||||
}
|
||||
|
||||
c.ResponseOk(respUser, contentType)
|
||||
@ -224,7 +230,7 @@ func (c *ApiController) SetPassword() {
|
||||
|
||||
requestUserId := c.GetSessionUsername()
|
||||
if requestUserId == "" {
|
||||
c.ResponseError("Please login first.")
|
||||
c.ResponseError("Please login first")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@ -62,7 +62,7 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
|
||||
}
|
||||
|
||||
func getInitScore() int {
|
||||
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
|
||||
score, err := strconv.Atoi(conf.GetConfigString("initScore"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -68,15 +68,22 @@ func (c *ApiController) SendVerificationCode() {
|
||||
organization := object.GetOrganization(orgId)
|
||||
application := object.GetApplicationByOrganizationName(organization.Name)
|
||||
|
||||
if checkUser == "true" && user == nil &&
|
||||
object.GetUserByFields(organization.Name, dest) == nil {
|
||||
c.ResponseError("No such user.")
|
||||
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
|
||||
c.ResponseError("Please login first")
|
||||
return
|
||||
}
|
||||
|
||||
sendResp := errors.New("Invalid dest type.")
|
||||
sendResp := errors.New("Invalid dest type")
|
||||
|
||||
if user == nil && checkUser != "" && checkUser != "true" {
|
||||
_, name := util.GetOwnerAndNameFromId(orgId)
|
||||
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
|
||||
}
|
||||
switch destType {
|
||||
case "email":
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == dest {
|
||||
dest = user.Email
|
||||
}
|
||||
if !util.IsEmailValid(dest) {
|
||||
c.ResponseError("Invalid Email address")
|
||||
return
|
||||
@ -85,6 +92,9 @@ func (c *ApiController) SendVerificationCode() {
|
||||
provider := application.GetEmailProvider()
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
case "phone":
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||
dest = user.Phone
|
||||
}
|
||||
if !util.IsPhoneCnValid(dest) {
|
||||
c.ResponseError("Invalid phone number")
|
||||
return
|
||||
@ -121,7 +131,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||
return
|
||||
}
|
||||
|
||||
|
5
go.mod
5
go.mod
@ -9,10 +9,11 @@ require (
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1
|
||||
github.com/casdoor/go-sms-sender v0.0.5
|
||||
github.com/casdoor/go-sms-sender v0.2.0
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
github.com/go-pay/gopay v1.5.72
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0
|
||||
github.com/google/uuid v1.2.0
|
||||
@ -29,7 +30,7 @@ require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
|
8
go.sum
8
go.sum
@ -81,6 +81,8 @@ github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0B
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
|
||||
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
|
||||
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
|
||||
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
|
||||
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@ -124,6 +126,8 @@ github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ
|
||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
|
||||
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
|
||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
@ -379,8 +383,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
|
||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
@ -88,7 +88,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@ -97,7 +97,7 @@ func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
158
idp/casdoor.go
Normal file
158
idp/casdoor.go
Normal file
@ -0,0 +1,158 @@
|
||||
// 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type CasdoorIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
Host string
|
||||
}
|
||||
|
||||
func NewCasdoorIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *CasdoorIdProvider {
|
||||
idp := &CasdoorIdProvider{}
|
||||
config := idp.getConfig(hostUrl)
|
||||
config.ClientID = clientId
|
||||
config.ClientSecret = clientSecret
|
||||
config.RedirectURL = redirectUrl
|
||||
idp.Config = config
|
||||
idp.Host = hostUrl
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *CasdoorIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *CasdoorIdProvider) getConfig(hostUrl string) *oauth2.Config {
|
||||
return &oauth2.Config{
|
||||
Endpoint: oauth2.Endpoint{
|
||||
TokenURL: hostUrl + "/api/login/oauth/access_token",
|
||||
},
|
||||
Scopes: []string{"openid email profile"},
|
||||
}
|
||||
}
|
||||
|
||||
type CasdoorToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
resp, err := http.PostForm(idp.Config.Endpoint.TokenURL, url.Values{
|
||||
"client_id": {idp.Config.ClientID},
|
||||
"client_secret": {idp.Config.ClientSecret},
|
||||
"code": {code},
|
||||
"grant_type": {"authorization_code"},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pToken := &CasdoorToken{}
|
||||
err = json.Unmarshal(body, pToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//check if token is expired
|
||||
if pToken.ExpiresIn <= 0 {
|
||||
return nil, fmt.Errorf("%s", pToken.AccessToken)
|
||||
}
|
||||
token := &oauth2.Token{
|
||||
AccessToken: pToken.AccessToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||
}
|
||||
return token, nil
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"sub": "2f80c349-4beb-407f-b1f0-528aac0f1acd",
|
||||
"iss": "https://door.casbin.com",
|
||||
"aud": "7a11****0fa2172",
|
||||
"name": "admin",
|
||||
"preferred_username": "Admin",
|
||||
"email": "admin@example.com",
|
||||
"picture": "https://casbin.org/img/casbin.svg",
|
||||
"address": "Guangdong",
|
||||
"phone": "12345678910"
|
||||
}
|
||||
*/
|
||||
|
||||
type CasdoorUserInfo struct {
|
||||
Id string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"preferred_username"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"picture"`
|
||||
Status string `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
cdUserinfo := &CasdoorUserInfo{}
|
||||
accessToken := token.AccessToken
|
||||
request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/userinfo", idp.Host), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//add accesstoken to bearer token
|
||||
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
||||
resp, err := idp.Client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, cdUserinfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cdUserinfo.Status != "" {
|
||||
return nil, fmt.Errorf("err: %s", cdUserinfo.Msg)
|
||||
}
|
||||
|
||||
userInfo := &UserInfo{
|
||||
Id: cdUserinfo.Id,
|
||||
Username: cdUserinfo.Name,
|
||||
DisplayName: cdUserinfo.DisplayName,
|
||||
Email: cdUserinfo.Email,
|
||||
AvatarUrl: cdUserinfo.AvatarUrl,
|
||||
}
|
||||
return userInfo, nil
|
||||
|
||||
}
|
@ -18,6 +18,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -143,7 +144,7 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -178,7 +179,7 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -92,7 +93,7 @@ func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rbs, err := io.ReadAll(resp.Body)
|
||||
rbs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package idp
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -172,7 +172,7 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package idp
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -85,7 +85,7 @@ func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -209,7 +209,7 @@ func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@ -95,7 +95,7 @@ func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package idp
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@ -69,7 +69,7 @@ func (idp *InfoflowInternalIdProvider) GetToken(code string) (*oauth2.Token, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -147,7 +147,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -165,7 +165,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
data, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -143,7 +144,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -161,7 +162,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
data, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -196,7 +197,7 @@ func (idp *InfoflowIdProvider) postWithBody(body interface{}, url string) ([]byt
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package idp
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -168,7 +169,7 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
||||
|
||||
resp, err := idp.Client.Do(req)
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
data, err = ioutil.ReadAll(resp.Body)
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -200,7 +201,7 @@ func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -84,7 +85,7 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rbs, err := io.ReadAll(resp.Body)
|
||||
rbs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -322,7 +323,7 @@ func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -78,6 +78,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else if typ == "Casdoor" {
|
||||
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||
} else if isGothSupport(typ) {
|
||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
@ -75,7 +75,7 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
tokenContent, err := io.ReadAll(resp.Body)
|
||||
tokenContent, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
re := regexp.MustCompile("token=(.*?)&")
|
||||
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
|
||||
@ -145,7 +145,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
openIdBody, err := io.ReadAll(resp.Body)
|
||||
openIdBody, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
|
||||
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
|
||||
@ -161,7 +161,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
userInfoBody, err := io.ReadAll(resp.Body)
|
||||
userInfoBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package idp
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@ -72,7 +72,7 @@ func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -122,7 +122,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -143,7 +143,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
data, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -194,7 +195,7 @@ func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -91,7 +92,7 @@ func (idp *WeiBoIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
6
main.go
6
main.go
@ -22,6 +22,7 @@ import (
|
||||
"github.com/astaxie/beego/logs"
|
||||
_ "github.com/astaxie/beego/session/redis"
|
||||
"github.com/casdoor/casdoor/authz"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/routers"
|
||||
@ -31,6 +32,7 @@ import (
|
||||
func main() {
|
||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
||||
flag.Parse()
|
||||
|
||||
object.InitAdapter(*createDatabase)
|
||||
object.InitDb()
|
||||
object.InitDefaultStorageProvider()
|
||||
@ -52,12 +54,12 @@ func main() {
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||
|
||||
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
||||
if beego.AppConfig.String("redisEndpoint") == "" {
|
||||
if conf.GetConfigString("redisEndpoint") == "" {
|
||||
beego.BConfig.WebConfig.Session.SessionProvider = "file"
|
||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
|
||||
} else {
|
||||
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
|
||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = beego.AppConfig.String("redisEndpoint")
|
||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
||||
}
|
||||
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
||||
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
||||
|
@ -16,7 +16,7 @@ data:
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
httpProxy = "127.0.0.1:10808"
|
||||
sock5Proxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
logPostOnly = true
|
||||
|
@ -17,7 +17,6 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"xorm.io/core"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@ -25,6 +24,7 @@ import (
|
||||
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
//_ "github.com/lib/pq" // db = postgres
|
||||
"xorm.io/core"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@ -41,7 +41,7 @@ func InitConfig() {
|
||||
|
||||
func InitAdapter(createDatabase bool) {
|
||||
|
||||
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
|
||||
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName"))
|
||||
if createDatabase {
|
||||
adapter.CreateDatabase()
|
||||
}
|
||||
@ -111,10 +111,10 @@ func (a *Adapter) close() {
|
||||
}
|
||||
|
||||
func (a *Adapter) createTable() {
|
||||
showSql, _ := beego.AppConfig.Bool("showSql")
|
||||
showSql, _ := conf.GetConfigBool("showSql")
|
||||
a.Engine.ShowSQL(showSql)
|
||||
|
||||
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
||||
a.Engine.SetTableMapper(tbMapper)
|
||||
|
||||
@ -183,6 +183,11 @@ func (a *Adapter) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Product))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Payment))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
@ -38,6 +39,7 @@ type Application struct {
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
@ -215,6 +217,18 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
||||
if application.ClientSecret != "" {
|
||||
application.ClientSecret = "***"
|
||||
}
|
||||
|
||||
if application.OrganizationObj != nil {
|
||||
if application.OrganizationObj.MasterPassword != "" {
|
||||
application.OrganizationObj.MasterPassword = "***"
|
||||
}
|
||||
if application.OrganizationObj.PasswordType != "" {
|
||||
application.OrganizationObj.PasswordType = "***"
|
||||
}
|
||||
if application.OrganizationObj.PasswordSalt != "" {
|
||||
application.OrganizationObj.PasswordSalt = "***"
|
||||
}
|
||||
}
|
||||
return application
|
||||
}
|
||||
|
||||
@ -282,3 +296,15 @@ func DeleteApplication(application *Application) bool {
|
||||
func (application *Application) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", application.Owner, application.Name)
|
||||
}
|
||||
|
||||
func CheckRedirectUriValid(application *Application, redirectUri string) bool {
|
||||
var validUri = false
|
||||
for _, tmpUri := range application.RedirectUris {
|
||||
fmt.Println(tmpUri, redirectUri)
|
||||
if strings.Contains(redirectUri, tmpUri) {
|
||||
validUri = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return validUri
|
||||
}
|
||||
|
@ -19,14 +19,14 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
var defaultStorageProvider *Provider = nil
|
||||
|
||||
func InitDefaultStorageProvider() {
|
||||
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider")
|
||||
defaultStorageProviderStr := conf.GetConfigString("defaultStorageProvider")
|
||||
if defaultStorageProviderStr != "" {
|
||||
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
|
||||
}
|
||||
|
@ -33,8 +33,10 @@ type Cert struct {
|
||||
BitSize int `json:"bitSize"`
|
||||
ExpireInYears int `json:"expireInYears"`
|
||||
|
||||
PublicKey string `xorm:"mediumtext" json:"publicKey"`
|
||||
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||
PublicKey string `xorm:"mediumtext" json:"publicKey"`
|
||||
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
|
||||
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
|
||||
}
|
||||
|
||||
func GetMaskedCert(cert *Cert) *Cert {
|
||||
|
@ -33,7 +33,7 @@ func init() {
|
||||
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||
}
|
||||
|
||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
|
||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, affiliation string) string {
|
||||
if organization == nil {
|
||||
return "organization does not exist"
|
||||
}
|
||||
@ -85,11 +85,19 @@ func CheckUserSignup(application *Application, organization *Organization, usern
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Display name") {
|
||||
if displayName == "" {
|
||||
return "displayName cannot be blank"
|
||||
} else if application.GetSignupItemRule("Display name") == "Personal" {
|
||||
if !isValidPersonalName(displayName) {
|
||||
return "displayName is not valid personal name"
|
||||
if application.GetSignupItemRule("Display name") == "First, last" && (firstName != "" || lastName != "") {
|
||||
if firstName == "" {
|
||||
return "firstName cannot be blank"
|
||||
} else if lastName == "" {
|
||||
return "lastName cannot be blank"
|
||||
}
|
||||
} else {
|
||||
if displayName == "" {
|
||||
return "displayName cannot be blank"
|
||||
} else if application.GetSignupItemRule("Display name") == "Real name" {
|
||||
if !isValidRealName(displayName) {
|
||||
return "displayName is not valid real name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -171,13 +179,14 @@ func CheckUserPassword(organization string, username string, password string) (*
|
||||
if user.IsForbidden {
|
||||
return nil, "the user is forbidden to sign in, please contact the administrator"
|
||||
}
|
||||
//for ldap users
|
||||
if user.Ldap != "" {
|
||||
return checkLdapUserPassword(user, password)
|
||||
}
|
||||
|
||||
msg := CheckPassword(user, password)
|
||||
if msg != "" {
|
||||
//for ldap users
|
||||
if user.Ldap != "" {
|
||||
return checkLdapUserPassword(user, password)
|
||||
}
|
||||
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
|
@ -16,16 +16,16 @@ package object
|
||||
|
||||
import "regexp"
|
||||
|
||||
var rePersonalName *regexp.Regexp
|
||||
var reRealName *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
rePersonalName, err = regexp.Compile("^[\u4E00-\u9FA5]{2,3}(?:·[\u4E00-\u9FA5]{2,3})*$")
|
||||
reRealName, err = regexp.Compile("^[\u4E00-\u9FA5]{2,3}(?:·[\u4E00-\u9FA5]{2,3})*$")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func isValidPersonalName(s string) bool {
|
||||
return rePersonalName.MatchString(s)
|
||||
func isValidRealName(s string) bool {
|
||||
return reRealName.MatchString(s)
|
||||
}
|
||||
|
@ -15,17 +15,11 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
//go:embed token_jwt_key.pem
|
||||
var tokenJwtPublicKey string
|
||||
|
||||
//go:embed token_jwt_key.key
|
||||
var tokenJwtPrivateKey string
|
||||
|
||||
func InitDb() {
|
||||
initBuiltInOrganization()
|
||||
initBuiltInUser()
|
||||
@ -47,9 +41,10 @@ func initBuiltInOrganization() {
|
||||
DisplayName: "Built-in Organization",
|
||||
WebsiteUrl: "https://example.com",
|
||||
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
||||
PasswordType: "plain",
|
||||
PhonePrefix: "86",
|
||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||
PasswordType: "plain",
|
||||
Tags: []string{},
|
||||
}
|
||||
AddOrganization(organization)
|
||||
}
|
||||
@ -121,7 +116,22 @@ func initBuiltInApplication() {
|
||||
AddApplication(application)
|
||||
}
|
||||
|
||||
func readTokenFromFile() (string, string) {
|
||||
pemPath := "./object/token_jwt_key.pem"
|
||||
keyPath := "./object/token_jwt_key.key"
|
||||
pem, err := ioutil.ReadFile(pemPath)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
key, err := ioutil.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
return string(pem), string(key)
|
||||
}
|
||||
|
||||
func initBuiltInCert() {
|
||||
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile()
|
||||
cert := getCert("admin", "cert-built-in")
|
||||
if cert != nil {
|
||||
return
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
@ -30,6 +30,7 @@ type OidcDiscovery struct {
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
||||
JwksUri string `json:"jwks_uri"`
|
||||
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported"`
|
||||
ResponseModesSupported []string `json:"response_modes_supported"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported"`
|
||||
@ -57,7 +58,7 @@ func getOriginFromHost(host string) (string, string) {
|
||||
func GetOidcDiscovery(host string) OidcDiscovery {
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
|
||||
origin := beego.AppConfig.String("origin")
|
||||
origin := conf.GetConfigString("origin")
|
||||
if origin != "" {
|
||||
originFrontend = origin
|
||||
originBackend = origin
|
||||
@ -74,6 +75,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
||||
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
|
||||
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
||||
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
||||
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
||||
ResponseTypesSupported: []string{"id_token"},
|
||||
ResponseModesSupported: []string{"login", "code", "link"},
|
||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||
|
@ -25,15 +25,16 @@ type Organization struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
}
|
||||
|
||||
func GetOrganizationCount(owner, field, value string) int {
|
||||
|
@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
@ -27,15 +28,22 @@ type Payment struct {
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
Provider string `xorm:"varchar(100)" json:"provider"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Good string `xorm:"varchar(100)" json:"good"`
|
||||
Amount string `xorm:"varchar(100)" json:"amount"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
Provider string `xorm:"varchar(100)" json:"provider"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
ProductName string `xorm:"varchar(100)" json:"productName"`
|
||||
ProductDisplayName string `xorm:"varchar(100)" json:"productDisplayName"`
|
||||
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
Detail string `xorm:"varchar(100)" json:"detail"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
Price float64 `json:"price"`
|
||||
|
||||
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
Message string `xorm:"varchar(1000)" json:"message"`
|
||||
}
|
||||
|
||||
func GetPaymentCount(owner, field, value string) int {
|
||||
@ -58,6 +66,16 @@ func GetPayments(owner string) []*Payment {
|
||||
return payments
|
||||
}
|
||||
|
||||
func GetUserPayments(owner string, organization string, user string) []*Payment {
|
||||
payments := []*Payment{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&payments, &Payment{Owner: owner, Organization: organization, User: user})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return payments
|
||||
}
|
||||
|
||||
func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Payment {
|
||||
payments := []*Payment{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
@ -124,6 +142,61 @@ func DeletePayment(payment *Payment) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error) {
|
||||
payment := getPayment(owner, paymentName)
|
||||
if payment == nil {
|
||||
return nil, fmt.Errorf("the payment: %s does not exist", paymentName)
|
||||
}
|
||||
|
||||
product := getProduct(owner, productName)
|
||||
if product == nil {
|
||||
return nil, fmt.Errorf("the product: %s does not exist", productName)
|
||||
}
|
||||
|
||||
provider, err := product.getProvider(providerName)
|
||||
if err != nil {
|
||||
return payment, err
|
||||
}
|
||||
|
||||
pProvider, cert, err := provider.getPaymentProvider()
|
||||
if err != nil {
|
||||
return payment, err
|
||||
}
|
||||
|
||||
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey)
|
||||
if err != nil {
|
||||
return payment, err
|
||||
}
|
||||
|
||||
if productDisplayName != "" && productDisplayName != product.DisplayName {
|
||||
return nil, fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productDisplayName, product.DisplayName)
|
||||
}
|
||||
|
||||
if price != product.Price {
|
||||
return nil, fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price)
|
||||
}
|
||||
|
||||
return payment, nil
|
||||
}
|
||||
|
||||
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) bool {
|
||||
payment, err := notifyPayment(request, body, owner, providerName, productName, paymentName)
|
||||
|
||||
if payment != nil {
|
||||
if err != nil {
|
||||
payment.State = "Error"
|
||||
payment.Message = err.Error()
|
||||
} else {
|
||||
payment.State = "Paid"
|
||||
}
|
||||
|
||||
UpdatePayment(payment.GetId(), payment)
|
||||
}
|
||||
|
||||
ok := err == nil
|
||||
return ok
|
||||
}
|
||||
|
||||
func (payment *Payment) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
||||
}
|
||||
|
210
object/product.go
Normal file
210
object/product.go
Normal file
@ -0,0 +1,210 @@
|
||||
// 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"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Product 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"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
Image string `xorm:"varchar(100)" json:"image"`
|
||||
Detail string `xorm:"varchar(100)" json:"detail"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
Price float64 `json:"price"`
|
||||
Quantity int `json:"quantity"`
|
||||
Sold int `json:"sold"`
|
||||
Providers []string `xorm:"varchar(100)" json:"providers"`
|
||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
}
|
||||
|
||||
func GetProductCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Product{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetProducts(owner string) []*Product {
|
||||
products := []*Product{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&products, &Product{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
func GetPaginationProducts(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Product {
|
||||
products := []*Product{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&products)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
func getProduct(owner string, name string) *Product {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
product := Product{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&product)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &product
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetProduct(id string) *Product {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getProduct(owner, name)
|
||||
}
|
||||
|
||||
func UpdateProduct(id string, product *Product) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getProduct(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(product)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddProduct(product *Product) bool {
|
||||
affected, err := adapter.Engine.Insert(product)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteProduct(product *Product) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{product.Owner, product.Name}).Delete(&Product{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (product *Product) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", product.Owner, product.Name)
|
||||
}
|
||||
|
||||
func (product *Product) isValidProvider(provider *Provider) bool {
|
||||
for _, providerName := range product.Providers {
|
||||
if providerName == provider.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (product *Product) getProvider(providerId string) (*Provider, error) {
|
||||
provider := getProvider(product.Owner, providerId)
|
||||
if provider == nil {
|
||||
return nil, fmt.Errorf("the payment provider: %s does not exist", providerId)
|
||||
}
|
||||
|
||||
if !product.isValidProvider(provider) {
|
||||
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, product.Name)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func BuyProduct(id string, providerName string, user *User, host string) (string, error) {
|
||||
product := GetProduct(id)
|
||||
if product == nil {
|
||||
return "", fmt.Errorf("the product: %s does not exist", id)
|
||||
}
|
||||
|
||||
provider, err := product.getProvider(providerName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pProvider, _, err := provider.getPaymentProvider()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
owner := product.Owner
|
||||
productName := product.Name
|
||||
paymentName := util.GenerateTimeId()
|
||||
productDisplayName := product.DisplayName
|
||||
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
|
||||
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
|
||||
|
||||
payUrl, err := pProvider.Pay(providerName, productName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
payment := Payment{
|
||||
Owner: product.Owner,
|
||||
Name: paymentName,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: paymentName,
|
||||
Provider: provider.Name,
|
||||
Type: provider.Type,
|
||||
Organization: user.Owner,
|
||||
User: user.Name,
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
Detail: product.Detail,
|
||||
Tag: product.Tag,
|
||||
Currency: product.Currency,
|
||||
Price: product.Price,
|
||||
PayUrl: payUrl,
|
||||
ReturnUrl: product.ReturnUrl,
|
||||
State: "Created",
|
||||
}
|
||||
affected := AddPayment(&payment)
|
||||
if !affected {
|
||||
return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||
}
|
||||
|
||||
return payUrl, err
|
||||
}
|
44
object/product_test.go
Normal file
44
object/product_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
// 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.
|
||||
|
||||
//go:build !skipCi
|
||||
// +build !skipCi
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/casdoor/casdoor/pp"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func TestProduct(t *testing.T) {
|
||||
InitConfig()
|
||||
|
||||
product := GetProduct("admin/product_123")
|
||||
provider := getProvider(product.Owner, "provider_pay_alipay")
|
||||
cert := getCert(product.Owner, "cert-pay-alipay")
|
||||
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||
|
||||
paymentId := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
notifyUrl := ""
|
||||
payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
println(payUrl)
|
||||
}
|
@ -17,6 +17,7 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/pp"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
@ -35,6 +36,7 @@ type Provider struct {
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
@ -181,6 +183,23 @@ func DeleteProvider(provider *Provider) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
||||
cert := &Cert{}
|
||||
if p.Cert != "" {
|
||||
cert = getCert(p.Owner, p.Cert)
|
||||
if cert == nil {
|
||||
return nil, nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
|
||||
}
|
||||
}
|
||||
|
||||
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||
if pProvider == nil {
|
||||
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||
}
|
||||
|
||||
return pProvider, cert, nil
|
||||
}
|
||||
|
||||
func (p *Provider) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
||||
|
@ -18,8 +18,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
@ -27,7 +27,7 @@ var logPostOnly bool
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
logPostOnly, err = beego.AppConfig.Bool("logPostOnly")
|
||||
logPostOnly, err = conf.GetConfigBool("logPostOnly")
|
||||
if err != nil {
|
||||
//panic(err)
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
|
||||
type Resource struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
Name string `xorm:"varchar(200) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
saml2 "github.com/russellhaering/gosaml2"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
@ -73,7 +73,7 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
|
||||
certStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{},
|
||||
}
|
||||
origin := beego.AppConfig.String("origin")
|
||||
origin := conf.GetConfigString("origin")
|
||||
certEncodedData := ""
|
||||
if samlResponse != "" {
|
||||
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
||||
|
@ -18,6 +18,9 @@ import "github.com/casdoor/go-sms-sender"
|
||||
|
||||
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
||||
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
||||
if provider.Type == go_sms_sender.HuaweiCloud {
|
||||
client, err = go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/storage"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@ -28,7 +28,7 @@ var isCloudIntranet bool
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
isCloudIntranet, err = beego.AppConfig.Bool("isCloudIntranet")
|
||||
isCloudIntranet, err = conf.GetConfigBool("isCloudIntranet")
|
||||
if err != nil {
|
||||
//panic(err)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !skipCi
|
||||
// +build !skipCi
|
||||
|
||||
|
273
object/token.go
273
object/token.go
@ -17,6 +17,7 @@ package object
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@ -59,6 +60,21 @@ type TokenWrapper struct {
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
type IntrospectionResponse struct {
|
||||
Active bool `json:"active"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
ClientId string `json:"client_id,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
Exp int64 `json:"exp,omitempty"`
|
||||
Iat int64 `json:"iat,omitempty"`
|
||||
Nbf int64 `json:"nbf,omitempty"`
|
||||
Sub string `json:"sub,omitempty"`
|
||||
Aud []string `json:"aud,omitempty"`
|
||||
Iss string `json:"iss,omitempty"`
|
||||
Jti string `json:"jti,omitempty"`
|
||||
}
|
||||
|
||||
func GetTokenCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Token{})
|
||||
@ -168,6 +184,25 @@ func DeleteToken(token *Token) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteTokenByAceessToken(accessToken string) (bool, *Application) {
|
||||
token := Token{AccessToken: accessToken}
|
||||
existed, err := adapter.Engine.Get(&token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return false, nil
|
||||
}
|
||||
application := getApplication(token.Owner, token.Application)
|
||||
affected, err := adapter.Engine.Where("access_token=?", accessToken).Delete(&Token{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0, application
|
||||
}
|
||||
|
||||
func GetTokenByAccessToken(accessToken string) *Token {
|
||||
//Check if the accessToken is in the database
|
||||
token := Token{AccessToken: accessToken}
|
||||
@ -178,9 +213,18 @@ func GetTokenByAccessToken(accessToken string) *Token {
|
||||
return &token
|
||||
}
|
||||
|
||||
func GetTokenByTokenAndApplication(token string, application string) *Token {
|
||||
tokenResult := Token{}
|
||||
existed, err := adapter.Engine.Where("(refresh_token = ? or access_token = ? ) and application = ?", token, token, application).Get(&tokenResult)
|
||||
if err != nil || !existed {
|
||||
return nil
|
||||
}
|
||||
return &tokenResult
|
||||
}
|
||||
|
||||
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
|
||||
if responseType != "code" {
|
||||
return "response_type should be \"code\"", nil
|
||||
if responseType != "code" && responseType != "token" && responseType != "id_token" {
|
||||
return fmt.Sprintf("error: grant_type: %s is not supported in this application", responseType), nil
|
||||
}
|
||||
|
||||
application := GetApplicationByClientId(clientId)
|
||||
@ -261,7 +305,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
||||
}
|
||||
}
|
||||
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string) *TokenWrapper {
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string) *TokenWrapper {
|
||||
application := GetApplicationByClientId(clientId)
|
||||
if application == nil {
|
||||
return &TokenWrapper{
|
||||
@ -272,75 +316,30 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
}
|
||||
}
|
||||
|
||||
if grantType != "authorization_code" {
|
||||
//Check if grantType is allowed in the current application
|
||||
if !IsGrantTypeValid(grantType, application.GrantTypes) {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: grant_type should be \"authorization_code\"",
|
||||
AccessToken: fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType),
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if code == "" {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: authorization code should not be empty",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
var token *Token
|
||||
var err error
|
||||
switch grantType {
|
||||
case "authorization_code": // Authorization Code Grant
|
||||
token, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
|
||||
case "password": // Resource Owner Password Credentials Grant
|
||||
token, err = GetPasswordToken(application, username, password, scope, host)
|
||||
case "client_credentials": // Client Credentials Grant
|
||||
token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||
}
|
||||
|
||||
token := getTokenByCode(code)
|
||||
if token == nil {
|
||||
if err != nil {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: invalid authorization code",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if application.Name != token.Application {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: the token is for wrong application (client_id)",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if application.ClientSecret != clientSecret {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: invalid client_secret",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: incorrect code_verifier",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if token.CodeIsUsed {
|
||||
// anti replay attacks
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: authorization code has been used",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if time.Now().Unix() > token.CodeExpireIn {
|
||||
// code must be used within 5 minutes
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: authorization code has expired",
|
||||
AccessToken: err.Error(),
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
@ -380,7 +379,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
if application.ClientSecret != clientSecret {
|
||||
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: invalid client_secret",
|
||||
TokenType: "",
|
||||
@ -459,3 +458,151 @@ func pkceChallenge(verifier string) string {
|
||||
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
|
||||
return challenge
|
||||
}
|
||||
|
||||
// Check if grantType is allowed in the current application
|
||||
// authorization_code is allowed by default
|
||||
func IsGrantTypeValid(method string, grantTypes []string) bool {
|
||||
if method == "authorization_code" {
|
||||
return true
|
||||
}
|
||||
for _, m := range grantTypes {
|
||||
if m == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Authorization code flow
|
||||
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, error) {
|
||||
if code == "" {
|
||||
return nil, errors.New("error: authorization code should not be empty")
|
||||
}
|
||||
|
||||
token := getTokenByCode(code)
|
||||
if token == nil {
|
||||
return nil, errors.New("error: invalid authorization code")
|
||||
}
|
||||
if token.CodeIsUsed {
|
||||
// anti replay attacks
|
||||
return nil, errors.New("error: authorization code has been used")
|
||||
}
|
||||
|
||||
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
||||
return nil, errors.New("error: incorrect code_verifier")
|
||||
}
|
||||
|
||||
if application.ClientSecret != clientSecret {
|
||||
// when using PKCE, the Client Secret can be empty,
|
||||
// but if it is provided, it must be accurate.
|
||||
if token.CodeChallenge == "" {
|
||||
return nil, errors.New("error: invalid client_secret")
|
||||
} else {
|
||||
if clientSecret != "" {
|
||||
return nil, errors.New("error: invalid client_secret")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.Name != token.Application {
|
||||
return nil, errors.New("error: the token is for wrong application (client_id)")
|
||||
}
|
||||
|
||||
if time.Now().Unix() > token.CodeExpireIn {
|
||||
// code must be used within 5 minutes
|
||||
return nil, errors.New("error: authorization code has expired")
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Resource Owner Password Credentials flow
|
||||
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, error) {
|
||||
user := getUser(application.Organization, username)
|
||||
if user == nil {
|
||||
return nil, errors.New("error: the user does not exist")
|
||||
}
|
||||
if user.Password != password {
|
||||
return nil, errors.New("error: invalid username or password")
|
||||
}
|
||||
if user.IsForbidden {
|
||||
return nil, errors.New("error: the user is forbidden to sign in, please contact the administrator")
|
||||
}
|
||||
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token := &Token{
|
||||
Owner: application.Owner,
|
||||
Name: util.GenerateId(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Application: application.Name,
|
||||
Organization: user.Owner,
|
||||
User: user.Name,
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: application.ExpireInHours * 60,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
CodeIsUsed: true,
|
||||
}
|
||||
AddToken(token)
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Client Credentials flow
|
||||
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, error) {
|
||||
if application.ClientSecret != clientSecret {
|
||||
return nil, errors.New("error: invalid client_secret")
|
||||
}
|
||||
nullUser := &User{
|
||||
Owner: application.Owner,
|
||||
Id: application.GetId(),
|
||||
Name: fmt.Sprintf("app/%s", application.Name),
|
||||
}
|
||||
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token := &Token{
|
||||
Owner: application.Owner,
|
||||
Name: util.GenerateId(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Application: application.Name,
|
||||
Organization: application.Organization,
|
||||
User: nullUser.Name,
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
ExpiresIn: application.ExpireInHours * 60,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
CodeIsUsed: true,
|
||||
}
|
||||
AddToken(token)
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Implicit flow
|
||||
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
|
||||
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token := &Token{
|
||||
Owner: application.Owner,
|
||||
Name: util.GenerateId(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Application: application.Name,
|
||||
Organization: user.Owner,
|
||||
User: user.Name,
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: application.ExpireInHours * 60,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
CodeIsUsed: true,
|
||||
}
|
||||
AddToken(token)
|
||||
return token, nil
|
||||
}
|
||||
|
@ -15,11 +15,10 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
@ -67,7 +66,7 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
||||
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||
|
||||
user.Password = ""
|
||||
origin := beego.AppConfig.String("origin")
|
||||
origin := conf.GetConfigString("origin")
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
if origin != "" {
|
||||
originBackend = origin
|
||||
@ -147,3 +146,7 @@ func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func ParseJwtTokenByApplication(token string, application *Application) (*Claims, error) {
|
||||
return ParseJwtToken(token, getCertByApplication(application))
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
@ -34,6 +34,8 @@ type User struct {
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
||||
LastName string `xorm:"varchar(100)" json:"lastName"`
|
||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100) index" json:"email"`
|
||||
@ -53,6 +55,7 @@ type User struct {
|
||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||
Education string `xorm:"varchar(100)" json:"education"`
|
||||
Score int `json:"score"`
|
||||
Karma int `json:"karma"`
|
||||
Ranking int `json:"ranking"`
|
||||
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
||||
IsOnline bool `json:"isOnline"`
|
||||
@ -82,6 +85,7 @@ type User struct {
|
||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
||||
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
||||
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||
@ -349,6 +353,8 @@ func AddUser(user *User) bool {
|
||||
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
|
||||
user.Ranking = GetUserCount(user.Owner, "", "") + 1
|
||||
|
||||
affected, err := adapter.Engine.Insert(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -375,7 +381,9 @@ func AddUsers(users []*User) bool {
|
||||
|
||||
affected, err := adapter.Engine.Insert(users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if !strings.Contains(err.Error(), "Duplicate entry") {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
@ -421,7 +429,7 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
|
||||
if user == nil {
|
||||
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
|
||||
}
|
||||
origin := beego.AppConfig.String("origin")
|
||||
origin := conf.GetConfigString("origin")
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
if origin != "" {
|
||||
originBackend = origin
|
||||
|
@ -52,8 +52,8 @@ func UploadUsers(owner string, fileId string) bool {
|
||||
|
||||
oldUserMap := getUserMap(owner)
|
||||
newUsers := []*User{}
|
||||
for _, line := range table {
|
||||
if parseLineItem(&line, 0) == "" {
|
||||
for index, line := range table {
|
||||
if index == 0 || parseLineItem(&line, 0) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -67,38 +67,42 @@ func UploadUsers(owner string, fileId string) bool {
|
||||
Password: parseLineItem(&line, 6),
|
||||
PasswordSalt: parseLineItem(&line, 7),
|
||||
DisplayName: parseLineItem(&line, 8),
|
||||
Avatar: parseLineItem(&line, 9),
|
||||
FirstName: parseLineItem(&line, 9),
|
||||
LastName: parseLineItem(&line, 10),
|
||||
Avatar: parseLineItem(&line, 11),
|
||||
PermanentAvatar: "",
|
||||
Email: parseLineItem(&line, 10),
|
||||
Phone: parseLineItem(&line, 11),
|
||||
Location: parseLineItem(&line, 12),
|
||||
Address: []string{parseLineItem(&line, 13)},
|
||||
Affiliation: parseLineItem(&line, 14),
|
||||
Title: parseLineItem(&line, 15),
|
||||
IdCardType: parseLineItem(&line, 16),
|
||||
IdCard: parseLineItem(&line, 17),
|
||||
Homepage: parseLineItem(&line, 18),
|
||||
Bio: parseLineItem(&line, 19),
|
||||
Tag: parseLineItem(&line, 20),
|
||||
Region: parseLineItem(&line, 21),
|
||||
Language: parseLineItem(&line, 22),
|
||||
Gender: parseLineItem(&line, 23),
|
||||
Birthday: parseLineItem(&line, 24),
|
||||
Education: parseLineItem(&line, 25),
|
||||
Score: parseLineItemInt(&line, 26),
|
||||
Ranking: parseLineItemInt(&line, 27),
|
||||
Email: parseLineItem(&line, 12),
|
||||
Phone: parseLineItem(&line, 13),
|
||||
Location: parseLineItem(&line, 14),
|
||||
Address: []string{parseLineItem(&line, 15)},
|
||||
Affiliation: parseLineItem(&line, 16),
|
||||
Title: parseLineItem(&line, 17),
|
||||
IdCardType: parseLineItem(&line, 18),
|
||||
IdCard: parseLineItem(&line, 19),
|
||||
Homepage: parseLineItem(&line, 20),
|
||||
Bio: parseLineItem(&line, 21),
|
||||
Tag: parseLineItem(&line, 22),
|
||||
Region: parseLineItem(&line, 23),
|
||||
Language: parseLineItem(&line, 24),
|
||||
Gender: parseLineItem(&line, 25),
|
||||
Birthday: parseLineItem(&line, 26),
|
||||
Education: parseLineItem(&line, 27),
|
||||
Score: parseLineItemInt(&line, 28),
|
||||
Karma: parseLineItemInt(&line, 29),
|
||||
Ranking: parseLineItemInt(&line, 30),
|
||||
IsDefaultAvatar: false,
|
||||
IsOnline: parseLineItemBool(&line, 28),
|
||||
IsAdmin: parseLineItemBool(&line, 29),
|
||||
IsGlobalAdmin: parseLineItemBool(&line, 30),
|
||||
IsForbidden: parseLineItemBool(&line, 31),
|
||||
IsDeleted: parseLineItemBool(&line, 32),
|
||||
SignupApplication: parseLineItem(&line, 33),
|
||||
IsOnline: parseLineItemBool(&line, 31),
|
||||
IsAdmin: parseLineItemBool(&line, 32),
|
||||
IsGlobalAdmin: parseLineItemBool(&line, 33),
|
||||
IsForbidden: parseLineItemBool(&line, 34),
|
||||
IsDeleted: parseLineItemBool(&line, 35),
|
||||
SignupApplication: parseLineItem(&line, 36),
|
||||
Hash: "",
|
||||
PreHash: "",
|
||||
CreatedIp: parseLineItem(&line, 34),
|
||||
LastSigninTime: parseLineItem(&line, 35),
|
||||
LastSigninIp: parseLineItem(&line, 36),
|
||||
CreatedIp: parseLineItem(&line, 37),
|
||||
LastSigninTime: parseLineItem(&line, 38),
|
||||
LastSigninIp: parseLineItem(&line, 39),
|
||||
Ldap: "",
|
||||
Properties: map[string]string{},
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
@ -129,7 +129,7 @@ func CheckVerificationCode(dest, code string) string {
|
||||
return "Code has not been sent yet!"
|
||||
}
|
||||
|
||||
timeout, err := beego.AppConfig.Int64("verificationCodeTimeout")
|
||||
timeout, err := conf.GetConfigInt64("verificationCodeTimeout")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
92
pp/alipay.go
Normal file
92
pp/alipay.go
Normal file
@ -0,0 +1,92 @@
|
||||
// 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/alipay"
|
||||
)
|
||||
|
||||
type AlipayPaymentProvider struct {
|
||||
Client *alipay.Client
|
||||
}
|
||||
|
||||
func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider {
|
||||
pp := &AlipayPaymentProvider{}
|
||||
|
||||
client, err := alipay.NewClient(appId, appPrivateKey, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = client.SetCertSnByContent([]byte(appPublicKey), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pp.Client = client
|
||||
return pp
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName 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("subject", productDisplayName)
|
||||
bm.Set("out_trade_no", paymentName)
|
||||
bm.Set("total_amount", getPriceString(price))
|
||||
|
||||
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return payUrl, nil
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
bm, err := alipay.ParseNotifyToBodyMap(request)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
providerName := bm.Get("providerName")
|
||||
productName := bm.Get("productName")
|
||||
|
||||
productDisplayName := bm.Get("subject")
|
||||
paymentName := bm.Get("out_trade_no")
|
||||
price := util.ParseFloat(bm.Get("total_amount"))
|
||||
|
||||
ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
if !ok {
|
||||
return "", "", 0, "", "", fmt.Errorf("VerifySignWithCert() failed: %v", ok)
|
||||
}
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
}
|
232
pp/gc.go
Normal file
232
pp/gc.go
Normal file
@ -0,0 +1,232 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type GcPaymentProvider struct {
|
||||
Xmpch string
|
||||
SecretKey string
|
||||
Host string
|
||||
}
|
||||
|
||||
type GcPayReqInfo struct {
|
||||
OrderDate string `json:"orderdate"`
|
||||
OrderNo string `json:"orderno"`
|
||||
Amount string `json:"amount"`
|
||||
PayerId string `json:"payerid"`
|
||||
PayerName string `json:"payername"`
|
||||
Xmpch string `json:"xmpch"`
|
||||
ReturnUrl string `json:"return_url"`
|
||||
NotifyUrl string `json:"notify_url"`
|
||||
}
|
||||
|
||||
type GcPayRespInfo struct {
|
||||
Jylsh string `json:"jylsh"`
|
||||
Amount string `json:"amount"`
|
||||
PayerId string `json:"payerid"`
|
||||
PayerName string `json:"payername"`
|
||||
PayUrl string `json:"payurl"`
|
||||
}
|
||||
|
||||
type GcNotifyRespInfo struct {
|
||||
Xmpch string `json:"xmpch"`
|
||||
OrderDate string `json:"orderdate"`
|
||||
OrderNo string `json:"orderno"`
|
||||
Amount float64 `json:"amount"`
|
||||
Jylsh string `json:"jylsh"`
|
||||
TradeNo string `json:"tradeno"`
|
||||
PayMethod string `json:"paymethod"`
|
||||
OrderState string `json:"orderstate"`
|
||||
ReturnType string `json:"return_type"`
|
||||
PayerId string `json:"payerid"`
|
||||
PayerName string `json:"payername"`
|
||||
}
|
||||
|
||||
type GcRequestBody struct {
|
||||
Op string `json:"op"`
|
||||
Xmpch string `json:"xmpch"`
|
||||
Version string `json:"version"`
|
||||
Data string `json:"data"`
|
||||
RequestTime string `json:"requesttime"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
||||
type GcResponseBody struct {
|
||||
Op string `json:"op"`
|
||||
Xmpch string `json:"xmpch"`
|
||||
Version string `json:"version"`
|
||||
ReturnCode string `json:"return_code"`
|
||||
ReturnMsg string `json:"return_msg"`
|
||||
Data string `json:"data"`
|
||||
NotifyTime string `json:"notifytime"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
||||
func NewGcPaymentProvider(clientId string, clientSecret string, host string) *GcPaymentProvider {
|
||||
pp := &GcPaymentProvider{}
|
||||
|
||||
pp.Xmpch = clientId
|
||||
pp.SecretKey = clientSecret
|
||||
pp.Host = host
|
||||
return pp
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
contentType := "text/plain;charset=UTF-8"
|
||||
body := bytes.NewReader(postBytes)
|
||||
|
||||
req, err := http.NewRequest("POST", pp.Host, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
payReqInfo := GcPayReqInfo{
|
||||
OrderDate: util.GenerateSimpleTimeId(),
|
||||
OrderNo: util.GenerateTimeId(),
|
||||
Amount: getPriceString(price),
|
||||
PayerId: "",
|
||||
PayerName: "",
|
||||
Xmpch: pp.Xmpch,
|
||||
ReturnUrl: returnUrl,
|
||||
NotifyUrl: notifyUrl,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(payReqInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
body := GcRequestBody{
|
||||
Op: "OrderCreate",
|
||||
Xmpch: pp.Xmpch,
|
||||
Version: "1.4",
|
||||
Data: base64.StdEncoding.EncodeToString(b),
|
||||
RequestTime: util.GenerateSimpleTimeId(),
|
||||
}
|
||||
|
||||
params := fmt.Sprintf("data=%s&op=%s&requesttime=%s&version=%s&xmpch=%s%s", body.Data, body.Op, body.RequestTime, body.Version, body.Xmpch, pp.SecretKey)
|
||||
body.Sign = strings.ToUpper(util.GetMd5Hash(params))
|
||||
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
respBytes, err := pp.doPost(bodyBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var respBody GcResponseBody
|
||||
err = json.Unmarshal(respBytes, &respBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if respBody.ReturnCode != "SUCCESS" {
|
||||
return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
|
||||
}
|
||||
|
||||
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var payRespInfo GcPayRespInfo
|
||||
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return payRespInfo.PayUrl, nil
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
reqBody := GcRequestBody{}
|
||||
m, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
reqBody.Op = m["op"][0]
|
||||
reqBody.Xmpch = m["xmpch"][0]
|
||||
reqBody.Version = m["version"][0]
|
||||
reqBody.Data = m["data"][0]
|
||||
reqBody.RequestTime = m["requesttime"][0]
|
||||
reqBody.Sign = m["sign"][0]
|
||||
|
||||
notifyReqInfoBytes, err := base64.StdEncoding.DecodeString(reqBody.Data)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
var notifyRespInfo GcNotifyRespInfo
|
||||
err = json.Unmarshal(notifyReqInfoBytes, ¬ifyRespInfo)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
providerName := ""
|
||||
productName := ""
|
||||
|
||||
productDisplayName := ""
|
||||
paymentName := notifyRespInfo.OrderNo
|
||||
price := notifyRespInfo.Amount
|
||||
|
||||
if notifyRespInfo.OrderState != "1" {
|
||||
return "", "", 0, "", "", fmt.Errorf("error order state: %s", notifyRespInfo.OrderDate)
|
||||
}
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
}
|
31
pp/provider.go
Normal file
31
pp/provider.go
Normal file
@ -0,0 +1,31 @@
|
||||
// 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 "net/http"
|
||||
|
||||
type PaymentProvider interface {
|
||||
Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
|
||||
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
|
||||
}
|
||||
|
||||
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
|
||||
if typ == "Alipay" {
|
||||
return NewAlipayPaymentProvider(appId, appPublicKey, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
||||
} else if typ == "GC" {
|
||||
return NewGcPaymentProvider(appId, clientSecret, host)
|
||||
}
|
||||
return nil
|
||||
}
|
25
pp/util.go
Normal file
25
pp/util.go
Normal file
@ -0,0 +1,25 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getPriceString(price float64) string {
|
||||
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
|
||||
return priceString
|
||||
}
|
@ -21,7 +21,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
@ -54,17 +54,17 @@ func isAddressOpen(address string) bool {
|
||||
}
|
||||
|
||||
func getProxyHttpClient() *http.Client {
|
||||
httpProxy := beego.AppConfig.String("httpProxy")
|
||||
if httpProxy == "" {
|
||||
sock5Proxy := conf.GetConfigString("sock5Proxy")
|
||||
if sock5Proxy == "" {
|
||||
return &http.Client{}
|
||||
}
|
||||
|
||||
if !isAddressOpen(httpProxy) {
|
||||
if !isAddressOpen(sock5Proxy) {
|
||||
return &http.Client{}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
|
||||
dialer, err := proxy.SOCKS5("tcp", httpProxy, nil, proxy.Direct)
|
||||
dialer, err := proxy.SOCKS5("tcp", sock5Proxy, nil, proxy.Direct)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casdoor/casdoor/authz"
|
||||
@ -57,6 +58,8 @@ func getSubject(ctx *context.Context) (string, string) {
|
||||
|
||||
func getObject(ctx *context.Context) (string, string) {
|
||||
method := ctx.Request.Method
|
||||
path := ctx.Request.URL.Path
|
||||
|
||||
if method == http.MethodGet {
|
||||
// query == "?id=built-in/admin"
|
||||
id := ctx.Input.Query("id")
|
||||
@ -78,6 +81,14 @@ func getObject(ctx *context.Context) (string, string) {
|
||||
//panic(err)
|
||||
return "", ""
|
||||
}
|
||||
|
||||
if path == "/api/delete-resource" {
|
||||
tokens := strings.Split(obj.Name, "/")
|
||||
if len(tokens) >= 5 {
|
||||
obj.Name = tokens[4]
|
||||
}
|
||||
}
|
||||
|
||||
return obj.Owner, obj.Name
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,8 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
|
||||
// GET parameter like "/page?access_token=123" or
|
||||
// HTTP Bearer token like "Authorization: Bearer 123"
|
||||
accessToken := ctx.Input.Query("accessToken")
|
||||
if accessToken == "" {
|
||||
accessToken = parseBearerToken(ctx)
|
||||
}
|
||||
accessToken := util.GetMaxLenStr(ctx.Input.Query("accessToken"), ctx.Input.Query("access_token"), parseBearerToken(ctx))
|
||||
|
||||
if accessToken != "" {
|
||||
token := object.GetTokenByAccessToken(accessToken)
|
||||
if token == nil {
|
||||
@ -62,7 +60,7 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
// "/page?username=abc&password=123"
|
||||
userId = ctx.Input.Query("username")
|
||||
password := ctx.Input.Query("password")
|
||||
if userId != "" && password != "" {
|
||||
if userId != "" && password != "" && ctx.Input.Query("grant_type") == "" {
|
||||
owner, name := util.GetOwnerAndNameFromId(userId)
|
||||
_, msg := object.CheckUserPassword(owner, name, password)
|
||||
if msg != "" {
|
||||
|
@ -54,7 +54,7 @@ func getUserByClientIdSecret(ctx *context.Context) string {
|
||||
}
|
||||
|
||||
func RecordMessage(ctx *context.Context) {
|
||||
if ctx.Request.URL.Path == "/api/login" {
|
||||
if ctx.Request.URL.Path == "/api/login" || ctx.Request.URL.Path == "/api/signup" {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -127,6 +127,8 @@ func initAPI() {
|
||||
beego.Router("/api/login/oauth/code", &controllers.ApiController{}, "POST:GetOAuthCode")
|
||||
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
||||
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
|
||||
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
|
||||
beego.Router("/api/login/oauth/logout", &controllers.ApiController{}, "GET:TokenLogout")
|
||||
|
||||
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
|
||||
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
||||
@ -149,11 +151,20 @@ func initAPI() {
|
||||
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
||||
|
||||
beego.Router("/api/get-products", &controllers.ApiController{}, "GET:GetProducts")
|
||||
beego.Router("/api/get-product", &controllers.ApiController{}, "GET:GetProduct")
|
||||
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
|
||||
beego.Router("/api/add-product", &controllers.ApiController{}, "POST:AddProduct")
|
||||
beego.Router("/api/delete-product", &controllers.ApiController{}, "POST:DeleteProduct")
|
||||
beego.Router("/api/buy-product", &controllers.ApiController{}, "POST:BuyProduct")
|
||||
|
||||
beego.Router("/api/get-payments", &controllers.ApiController{}, "GET:GetPayments")
|
||||
beego.Router("/api/get-user-payments", &controllers.ApiController{}, "GET:GetUserPayments")
|
||||
beego.Router("/api/get-payment", &controllers.ApiController{}, "GET:GetPayment")
|
||||
beego.Router("/api/update-payment", &controllers.ApiController{}, "POST:UpdatePayment")
|
||||
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
|
||||
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
|
||||
beego.Router("/api/notify-payment/?:owner/?:provider/?:product/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
|
||||
|
||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||
|
@ -174,6 +174,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-product": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Product API"
|
||||
],
|
||||
"description": "add product",
|
||||
"operationId": "ApiController.AddProduct",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the product",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Product"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/add-provider": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -450,6 +478,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/buy-product": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Product API"
|
||||
],
|
||||
"description": "buy product",
|
||||
"operationId": "ApiController.BuyProduct",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id of the product",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "providerName",
|
||||
"description": "The name of the provider",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/check-ldap-users-exist": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -614,6 +675,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/delete-product": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Product API"
|
||||
],
|
||||
"description": "delete product",
|
||||
"operationId": "ApiController.DeleteProduct",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the product",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Product"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/delete-provider": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -1159,6 +1248,61 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-product": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Product API"
|
||||
],
|
||||
"description": "get product",
|
||||
"operationId": "ApiController.GetProduct",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id of the product",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Product"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-products": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Product API"
|
||||
],
|
||||
"description": "get products",
|
||||
"operationId": "ApiController.GetProducts",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "owner",
|
||||
"description": "The owner of products",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Product"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-provider": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -1599,6 +1743,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-user-payments": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Payment API"
|
||||
],
|
||||
"description": "get payments for a user",
|
||||
"operationId": "ApiController.GetUserPayments",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "owner",
|
||||
"description": "The owner of payments",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "organization",
|
||||
"description": "The organization of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "user",
|
||||
"description": "The username of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Payment"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-users": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -1825,8 +2012,80 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login/oauth/introspect": {
|
||||
"post": {
|
||||
"description": "The introspection endpoint is an OAuth 2.0 endpoint that takes a",
|
||||
"operationId": "ApiController.IntrospectToken",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "token",
|
||||
"description": "access_token's value or refresh_token's value",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "formData",
|
||||
"name": "token_type_hint",
|
||||
"description": "the token type access_token or refresh_token",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.IntrospectionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login/oauth/logout": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Token API"
|
||||
],
|
||||
"description": "delete token by AccessToken",
|
||||
"operationId": "ApiController.TokenLogout",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id_token_hint",
|
||||
"description": "id_token_hint",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "post_logout_redirect_uri",
|
||||
"description": "post_logout_redirect_uri",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "state",
|
||||
"description": "state",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login/oauth/refresh_token": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Token API"
|
||||
],
|
||||
"description": "refresh OAuth access token",
|
||||
"operationId": "ApiController.RefreshToken",
|
||||
"parameters": [
|
||||
@ -1862,7 +2121,6 @@
|
||||
"in": "query",
|
||||
"name": "client_secret",
|
||||
"description": "OAuth client secret",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
@ -1893,6 +2151,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/notify-payment": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Payment API"
|
||||
],
|
||||
"description": "notify payment",
|
||||
"operationId": "ApiController.NotifyPayment",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the payment",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Payment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/send-verification-code": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -2231,6 +2517,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update-product": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Product API"
|
||||
],
|
||||
"description": "update product",
|
||||
"operationId": "ApiController.UpdateProduct",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id of the product",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "The details of the product",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.Product"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/update-provider": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -2476,11 +2797,11 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"1867.0xc00029b560.false": {
|
||||
"2026.0xc000380de0.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
"1901.0xc00029b590.false": {
|
||||
"2060.0xc000380e10.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
@ -2497,10 +2818,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/1867.0xc00029b560.false"
|
||||
"$ref": "#/definitions/2026.0xc000380de0.false"
|
||||
},
|
||||
"data2": {
|
||||
"$ref": "#/definitions/1901.0xc00029b590.false"
|
||||
"$ref": "#/definitions/2060.0xc000380e10.false"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
@ -2521,10 +2842,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/1867.0xc00029b560.false"
|
||||
"$ref": "#/definitions/2026.0xc000380de0.false"
|
||||
},
|
||||
"data2": {
|
||||
"$ref": "#/definitions/1901.0xc00029b590.false"
|
||||
"$ref": "#/definitions/2060.0xc000380e10.false"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
@ -2606,6 +2927,12 @@
|
||||
"forgetUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"grantTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"homepageUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -2670,6 +2997,12 @@
|
||||
"title": "Cert",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"authorityPublicKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"authorityRootPublicKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"bitSize": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
@ -2719,6 +3052,54 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.IntrospectionResponse": {
|
||||
"title": "IntrospectionResponse",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"aud": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"exp": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"iat": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"iss": {
|
||||
"type": "string"
|
||||
},
|
||||
"jti": {
|
||||
"type": "string"
|
||||
},
|
||||
"nbf": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
},
|
||||
"sub": {
|
||||
"type": "string"
|
||||
},
|
||||
"token_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Organization": {
|
||||
"title": "Organization",
|
||||
"type": "object",
|
||||
@ -2756,6 +3137,12 @@
|
||||
"phonePrefix": {
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"websiteUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
@ -2765,19 +3152,19 @@
|
||||
"title": "Payment",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"detail": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"good": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@ -2789,12 +3176,31 @@
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"payUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"productDisplayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"productName": {
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"type": "string"
|
||||
},
|
||||
"returnUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -2854,6 +3260,60 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Product": {
|
||||
"title": "Product",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"detail": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"providers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"returnUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"sold": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.Provider": {
|
||||
"title": "Provider",
|
||||
"type": "object",
|
||||
@ -2867,6 +3327,9 @@
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"cert": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -3237,6 +3700,9 @@
|
||||
"birthday": {
|
||||
"type": "string"
|
||||
},
|
||||
"casdoor": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdIp": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -3258,6 +3724,9 @@
|
||||
"facebook": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"gender": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -3309,12 +3778,19 @@
|
||||
"isOnline": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"karma": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
"lark": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastSigninIp": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -112,6 +112,24 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-product:
|
||||
post:
|
||||
tags:
|
||||
- Product API
|
||||
description: add product
|
||||
operationId: ApiController.AddProduct
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the product
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Product'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/add-provider:
|
||||
post:
|
||||
tags:
|
||||
@ -291,6 +309,28 @@ paths:
|
||||
description: object
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
/api/buy-product:
|
||||
post:
|
||||
tags:
|
||||
- Product API
|
||||
description: buy product
|
||||
operationId: ApiController.BuyProduct
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id of the product
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: providerName
|
||||
description: The name of the provider
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/check-ldap-users-exist:
|
||||
post:
|
||||
tags:
|
||||
@ -396,6 +436,24 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/delete-product:
|
||||
post:
|
||||
tags:
|
||||
- Product API
|
||||
description: delete product
|
||||
operationId: ApiController.DeleteProduct
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the product
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Product'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/delete-provider:
|
||||
post:
|
||||
tags:
|
||||
@ -750,6 +808,42 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Permission'
|
||||
/api/get-product:
|
||||
get:
|
||||
tags:
|
||||
- Product API
|
||||
description: get product
|
||||
operationId: ApiController.GetProduct
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id of the product
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.Product'
|
||||
/api/get-products:
|
||||
get:
|
||||
tags:
|
||||
- Product API
|
||||
description: get products
|
||||
operationId: ApiController.GetProducts
|
||||
parameters:
|
||||
- in: query
|
||||
name: owner
|
||||
description: The owner of products
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Product'
|
||||
/api/get-provider:
|
||||
get:
|
||||
tags:
|
||||
@ -1039,6 +1133,35 @@ paths:
|
||||
responses:
|
||||
"200":
|
||||
description: '{int} int The count of filtered users for an organization'
|
||||
/api/get-user-payments:
|
||||
get:
|
||||
tags:
|
||||
- Payment API
|
||||
description: get payments for a user
|
||||
operationId: ApiController.GetUserPayments
|
||||
parameters:
|
||||
- in: query
|
||||
name: owner
|
||||
description: The owner of payments
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: organization
|
||||
description: The organization of the user
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: user
|
||||
description: The username of the user
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Payment'
|
||||
/api/get-users:
|
||||
get:
|
||||
tags:
|
||||
@ -1190,8 +1313,56 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.TokenWrapper'
|
||||
/api/login/oauth/introspect:
|
||||
post:
|
||||
description: The introspection endpoint is an OAuth 2.0 endpoint that takes a
|
||||
operationId: ApiController.IntrospectToken
|
||||
parameters:
|
||||
- in: formData
|
||||
name: token
|
||||
description: access_token's value or refresh_token's value
|
||||
required: true
|
||||
type: string
|
||||
- in: formData
|
||||
name: token_type_hint
|
||||
description: the token type access_token or refresh_token
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.IntrospectionResponse'
|
||||
/api/login/oauth/logout:
|
||||
get:
|
||||
tags:
|
||||
- Token API
|
||||
description: delete token by AccessToken
|
||||
operationId: ApiController.TokenLogout
|
||||
parameters:
|
||||
- in: query
|
||||
name: id_token_hint
|
||||
description: id_token_hint
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: post_logout_redirect_uri
|
||||
description: post_logout_redirect_uri
|
||||
type: string
|
||||
- in: query
|
||||
name: state
|
||||
description: state
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/login/oauth/refresh_token:
|
||||
post:
|
||||
tags:
|
||||
- Token API
|
||||
description: refresh OAuth access token
|
||||
operationId: ApiController.RefreshToken
|
||||
parameters:
|
||||
@ -1218,7 +1389,6 @@ paths:
|
||||
- in: query
|
||||
name: client_secret
|
||||
description: OAuth client secret
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
@ -1236,6 +1406,24 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/notify-payment:
|
||||
post:
|
||||
tags:
|
||||
- Payment API
|
||||
description: notify payment
|
||||
operationId: ApiController.NotifyPayment
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the payment
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Payment'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/send-verification-code:
|
||||
post:
|
||||
tags:
|
||||
@ -1460,6 +1648,29 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-product:
|
||||
post:
|
||||
tags:
|
||||
- Product API
|
||||
description: update product
|
||||
operationId: ApiController.UpdateProduct
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id of the product
|
||||
required: true
|
||||
type: string
|
||||
- in: body
|
||||
name: body
|
||||
description: The details of the product
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.Product'
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/update-provider:
|
||||
post:
|
||||
tags:
|
||||
@ -1620,10 +1831,10 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/object.Userinfo'
|
||||
definitions:
|
||||
1867.0xc00029b560.false:
|
||||
2026.0xc000380de0.false:
|
||||
title: "false"
|
||||
type: object
|
||||
1901.0xc00029b590.false:
|
||||
2060.0xc000380e10.false:
|
||||
title: "false"
|
||||
type: object
|
||||
RequestForm:
|
||||
@ -1637,9 +1848,9 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/1867.0xc00029b560.false'
|
||||
$ref: '#/definitions/2026.0xc000380de0.false'
|
||||
data2:
|
||||
$ref: '#/definitions/1901.0xc00029b590.false'
|
||||
$ref: '#/definitions/2060.0xc000380e10.false'
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
@ -1653,9 +1864,9 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/1867.0xc00029b560.false'
|
||||
$ref: '#/definitions/2026.0xc000380de0.false'
|
||||
data2:
|
||||
$ref: '#/definitions/1901.0xc00029b590.false'
|
||||
$ref: '#/definitions/2060.0xc000380e10.false'
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
@ -1710,6 +1921,10 @@ definitions:
|
||||
format: int64
|
||||
forgetUrl:
|
||||
type: string
|
||||
grantTypes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
homepageUrl:
|
||||
type: string
|
||||
logo:
|
||||
@ -1753,6 +1968,10 @@ definitions:
|
||||
title: Cert
|
||||
type: object
|
||||
properties:
|
||||
authorityPublicKey:
|
||||
type: string
|
||||
authorityRootPublicKey:
|
||||
type: string
|
||||
bitSize:
|
||||
type: integer
|
||||
format: int64
|
||||
@ -1785,6 +2004,39 @@ definitions:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
object.IntrospectionResponse:
|
||||
title: IntrospectionResponse
|
||||
type: object
|
||||
properties:
|
||||
active:
|
||||
type: boolean
|
||||
aud:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
client_id:
|
||||
type: string
|
||||
exp:
|
||||
type: integer
|
||||
format: int64
|
||||
iat:
|
||||
type: integer
|
||||
format: int64
|
||||
iss:
|
||||
type: string
|
||||
jti:
|
||||
type: string
|
||||
nbf:
|
||||
type: integer
|
||||
format: int64
|
||||
scope:
|
||||
type: string
|
||||
sub:
|
||||
type: string
|
||||
token_type:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
object.Organization:
|
||||
title: Organization
|
||||
type: object
|
||||
@ -1811,21 +2063,25 @@ definitions:
|
||||
type: string
|
||||
phonePrefix:
|
||||
type: string
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
websiteUrl:
|
||||
type: string
|
||||
object.Payment:
|
||||
title: Payment
|
||||
type: object
|
||||
properties:
|
||||
amount:
|
||||
type: string
|
||||
createdTime:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
detail:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
good:
|
||||
message:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
@ -1833,10 +2089,23 @@ definitions:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
payUrl:
|
||||
type: string
|
||||
price:
|
||||
type: number
|
||||
format: double
|
||||
productDisplayName:
|
||||
type: string
|
||||
productName:
|
||||
type: string
|
||||
provider:
|
||||
type: string
|
||||
returnUrl:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
user:
|
||||
@ -1875,6 +2144,43 @@ definitions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
object.Product:
|
||||
title: Product
|
||||
type: object
|
||||
properties:
|
||||
createdTime:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
detail:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
image:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
price:
|
||||
type: number
|
||||
format: double
|
||||
providers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
quantity:
|
||||
type: integer
|
||||
format: int64
|
||||
returnUrl:
|
||||
type: string
|
||||
sold:
|
||||
type: integer
|
||||
format: int64
|
||||
state:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
object.Provider:
|
||||
title: Provider
|
||||
type: object
|
||||
@ -1885,6 +2191,8 @@ definitions:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
cert:
|
||||
type: string
|
||||
clientId:
|
||||
type: string
|
||||
clientId2:
|
||||
@ -2134,6 +2442,8 @@ definitions:
|
||||
type: string
|
||||
birthday:
|
||||
type: string
|
||||
casdoor:
|
||||
type: string
|
||||
createdIp:
|
||||
type: string
|
||||
createdTime:
|
||||
@ -2148,6 +2458,8 @@ definitions:
|
||||
type: string
|
||||
facebook:
|
||||
type: string
|
||||
firstName:
|
||||
type: string
|
||||
gender:
|
||||
type: string
|
||||
gitee:
|
||||
@ -2182,10 +2494,15 @@ definitions:
|
||||
type: boolean
|
||||
isOnline:
|
||||
type: boolean
|
||||
karma:
|
||||
type: integer
|
||||
format: int64
|
||||
language:
|
||||
type: string
|
||||
lark:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
lastSigninIp:
|
||||
type: string
|
||||
lastSigninTime:
|
||||
|
@ -20,10 +20,12 @@ import (
|
||||
)
|
||||
|
||||
var rePhoneCn *regexp.Regexp
|
||||
var rePhone *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
// https://learnku.com/articles/31543
|
||||
rePhoneCn, _ = regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`)
|
||||
rePhone, _ = regexp.Compile("(\\d{3})\\d*(\\d{4})")
|
||||
}
|
||||
|
||||
func IsEmailValid(email string) bool {
|
||||
@ -34,3 +36,7 @@ func IsEmailValid(email string) bool {
|
||||
func IsPhoneCnValid(phone string) bool {
|
||||
return rePhoneCn.MatchString(phone)
|
||||
}
|
||||
|
||||
func getMaskedPhone(phone string) string {
|
||||
return rePhone.ReplaceAllString(phone, "$1****$2")
|
||||
}
|
||||
|
@ -20,9 +20,10 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@ -41,6 +42,15 @@ func ParseInt(s string) int {
|
||||
return i
|
||||
}
|
||||
|
||||
func ParseFloat(s string) float64 {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func ParseBool(s string) bool {
|
||||
i := ParseInt(s)
|
||||
return i != 0
|
||||
@ -88,6 +98,25 @@ func GenerateId() string {
|
||||
return uuid.NewString()
|
||||
}
|
||||
|
||||
func GenerateTimeId() string {
|
||||
timestamp := time.Now().Unix()
|
||||
tm := time.Unix(timestamp, 0)
|
||||
t := tm.Format("20060102_150405")
|
||||
|
||||
random := uuid.NewString()[0:7]
|
||||
|
||||
res := fmt.Sprintf("%s_%s", t, random)
|
||||
return res
|
||||
}
|
||||
|
||||
func GenerateSimpleTimeId() string {
|
||||
timestamp := time.Now().Unix()
|
||||
tm := time.Unix(timestamp, 0)
|
||||
t := tm.Format("20060102150405")
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func GetId(name string) string {
|
||||
return fmt.Sprintf("admin/%s", name)
|
||||
}
|
||||
@ -133,7 +162,7 @@ func GetMinLenStr(strs ...string) string {
|
||||
}
|
||||
|
||||
func ReadStringFromPath(path string) string {
|
||||
data, err := os.ReadFile(path)
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -142,7 +171,7 @@ func ReadStringFromPath(path string) string {
|
||||
}
|
||||
|
||||
func WriteStringToPath(s string, path string) {
|
||||
err := os.WriteFile(path, []byte(s), 0644)
|
||||
err := ioutil.WriteFile(path, []byte(s), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -177,3 +206,28 @@ func IsChinese(str string) bool {
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
func GetMaskedPhone(phone string) string {
|
||||
return getMaskedPhone(phone)
|
||||
}
|
||||
|
||||
func GetMaskedEmail(email string) string {
|
||||
if email == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
tokens := strings.Split(email, "@")
|
||||
username := maskString(tokens[0])
|
||||
domain := tokens[1]
|
||||
domainTokens := strings.Split(domain, ".")
|
||||
domainTokens[len(domainTokens)-2] = maskString(domainTokens[len(domainTokens)-2])
|
||||
return fmt.Sprintf("%s@%s", username, strings.Join(domainTokens, "."))
|
||||
}
|
||||
|
||||
func maskString(str string) string {
|
||||
if len(str) <= 2 {
|
||||
return str
|
||||
} else {
|
||||
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str)-2), str[len(str)-1])
|
||||
}
|
||||
}
|
||||
|
@ -245,3 +245,4 @@ func TestSnakeString(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,9 @@
|
||||
"react-i18next": "^11.8.7",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"react-social-login-buttons": "^3.4.0"
|
||||
"react-social-login-buttons": "^3.4.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"core-js": "^3.21.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env PORT=7001 craco start",
|
||||
@ -46,12 +48,14 @@
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
"not op_mini all",
|
||||
"ie > 8"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
"last 1 safari version",
|
||||
"ie > 8"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
|
105
web/src/App.js
105
web/src/App.js
@ -43,11 +43,16 @@ import SyncerListPage from "./SyncerListPage";
|
||||
import SyncerEditPage from "./SyncerEditPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import ProductListPage from "./ProductListPage";
|
||||
import ProductEditPage from "./ProductEditPage";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
import PaymentListPage from "./PaymentListPage";
|
||||
import PaymentEditPage from "./PaymentEditPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import HomePage from "./basic/HomePage";
|
||||
import CustomGithubCorner from "./CustomGithubCorner";
|
||||
import * as Conf from "./Conf";
|
||||
|
||||
import * as Auth from "./auth/Auth";
|
||||
import SignupPage from "./auth/SignupPage";
|
||||
@ -128,6 +133,8 @@ class App extends Component {
|
||||
this.setState({ selectedMenuKey: '/syncers' });
|
||||
} else if (uri.includes('/certs')) {
|
||||
this.setState({ selectedMenuKey: '/certs' });
|
||||
} else if (uri.includes('/products')) {
|
||||
this.setState({ selectedMenuKey: '/products' });
|
||||
} else if (uri.includes('/payments')) {
|
||||
this.setState({ selectedMenuKey: '/payments' });
|
||||
} else if (uri.includes('/signup')) {
|
||||
@ -141,16 +148,14 @@ class App extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getAccessTokenParam() {
|
||||
getAccessTokenParam(params) {
|
||||
// "/page?access_token=123"
|
||||
const params = new URLSearchParams(this.props.location.search);
|
||||
const accessToken = params.get("access_token");
|
||||
return accessToken === null ? "" : `?accessToken=${accessToken}`;
|
||||
}
|
||||
|
||||
getCredentialParams() {
|
||||
getCredentialParams(params) {
|
||||
// "/page?username=abc&password=123"
|
||||
const params = new URLSearchParams(this.props.location.search);
|
||||
if (params.get("username") === null || params.get("password") === null) {
|
||||
return "";
|
||||
}
|
||||
@ -158,8 +163,17 @@ class App extends Component {
|
||||
}
|
||||
|
||||
getUrlWithoutQuery() {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
return location.toString().replace(location.search, "");
|
||||
return window.location.toString().replace(window.location.search, "");
|
||||
}
|
||||
|
||||
getLanguageParam(params) {
|
||||
// "/page?language=en"
|
||||
const language = params.get("language");
|
||||
if (language !== null) {
|
||||
Setting.setLanguage(language);
|
||||
return `language=${language}`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
setLanguage(account) {
|
||||
@ -170,13 +184,23 @@ class App extends Component {
|
||||
}
|
||||
|
||||
getAccount() {
|
||||
let query = this.getAccessTokenParam();
|
||||
const params = new URLSearchParams(this.props.location.search);
|
||||
|
||||
let query = this.getAccessTokenParam(params);
|
||||
if (query === "") {
|
||||
query = this.getCredentialParams();
|
||||
query = this.getCredentialParams(params);
|
||||
}
|
||||
|
||||
const query2 = this.getLanguageParam(params);
|
||||
if (query2 !== "") {
|
||||
const url = window.location.toString().replace(new RegExp(`[?&]${query2}`), "");
|
||||
window.history.replaceState({}, document.title, url);
|
||||
}
|
||||
|
||||
if (query !== "") {
|
||||
window.history.replaceState({}, document.title, this.getUrlWithoutQuery());
|
||||
}
|
||||
|
||||
AuthBackend.getAccount(query)
|
||||
.then((res) => {
|
||||
let account = null;
|
||||
@ -211,8 +235,12 @@ class App extends Component {
|
||||
});
|
||||
|
||||
Setting.showMessage("success", `Logged out successfully`);
|
||||
|
||||
Setting.goToLinkSoft(this, "/");
|
||||
let redirectUri = res.data2;
|
||||
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||
Setting.goToLink(redirectUri);
|
||||
}else{
|
||||
Setting.goToLinkSoft(this, "/");
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
||||
}
|
||||
@ -412,13 +440,24 @@ class App extends Component {
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/payments">
|
||||
<Link to="/payments">
|
||||
{i18next.t("general:Payments")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
|
||||
if (Conf.EnableExtraPages) {
|
||||
res.push(
|
||||
<Menu.Item key="/products">
|
||||
<Link to="/products">
|
||||
{i18next.t("general:Products")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/payments">
|
||||
<Link to="/payments">
|
||||
{i18next.t("general:Payments")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
|
||||
res.push(
|
||||
<Menu.Item key="/swagger">
|
||||
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
|
||||
@ -490,8 +529,12 @@ class App extends Component {
|
||||
<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/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage 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/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
@ -514,22 +557,22 @@ class App extends Component {
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<Menu
|
||||
// theme="dark"
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{ lineHeight: '64px'}}
|
||||
>
|
||||
<div>
|
||||
<Menu
|
||||
// theme="dark"
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{lineHeight: '64px', width: '80%', position: 'absolute'}}
|
||||
>
|
||||
{
|
||||
this.renderMenu()
|
||||
}
|
||||
</Menu>
|
||||
{
|
||||
this.renderMenu()
|
||||
this.renderAccount()
|
||||
}
|
||||
<div style = {{float: 'right'}}>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox/>
|
||||
</div>
|
||||
</Menu>
|
||||
<SelectLanguageBox/>
|
||||
</div>
|
||||
</Header>
|
||||
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
|
||||
<Card className="content-warp-card">
|
||||
|
@ -61,6 +61,9 @@ class ApplicationEditPage extends React.Component {
|
||||
getApplication() {
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
if (application.grantTypes === null || application.grantTypes.length === 0) {
|
||||
application.grantTypes = ["authorization_code"];
|
||||
}
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
@ -163,12 +166,12 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel("Logo", i18next.t("general:Logo - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
URL:
|
||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.application.logo} onChange={e => {
|
||||
@ -435,6 +438,28 @@ class ApplicationEditPage extends React.Component {
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Grant types"), i18next.t("application:Grant types - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="tags" style={{width: '100%'}}
|
||||
value={this.state.application.grantTypes}
|
||||
onChange={(value => {
|
||||
this.updateApplicationField('grantTypes', value);
|
||||
})} >
|
||||
{
|
||||
[
|
||||
{id: "authorization_code", name: "Authorization Code"},
|
||||
{id: "password", name: "Password"},
|
||||
{id: "client_credentials", name: "Client Credentials"},
|
||||
{id: "token", name: "Token"},
|
||||
{id: "id_token",name:"ID Token"},
|
||||
].map((item, index)=><Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Providers"), i18next.t("general:Providers - Tooltip"))} :
|
||||
@ -494,13 +519,13 @@ class ApplicationEditPage extends React.Component {
|
||||
if (!Setting.isMobile()) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Col span={11} style={{display:'flex',flexDirection:'column'}}>
|
||||
<a style={{marginBottom: '10px',display:'flex'}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
||||
<Col span={11} style={{display:"flex", flexDirection: "column"}}>
|
||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
||||
<Button type="primary">{i18next.t("application:Test signup page..")}</Button>
|
||||
</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888" ,alignItems:'center',overflow:'auto',flexDirection:'column',flex:'auto'}}>
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||
{
|
||||
this.state.application.enablePassword ? (
|
||||
<SignupPage application={this.state.application} />
|
||||
@ -510,13 +535,13 @@ class ApplicationEditPage extends React.Component {
|
||||
}
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={11} style={{display:'flex',flexDirection:'column'}}>
|
||||
<a style={{marginBottom: '10px',display:'flex'}} target="_blank" rel="noreferrer" href={signInUrl}>
|
||||
<Col span={11} style={{display:"flex", flexDirection: "column"}}>
|
||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
||||
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
||||
</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888",alignItems:'center',overflow:'auto',flexDirection:'column',flex:'auto' }}>
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
|
||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
||||
</div>
|
||||
</Col>
|
||||
@ -525,11 +550,11 @@ class ApplicationEditPage extends React.Component {
|
||||
} else{
|
||||
return(
|
||||
<React.Fragment>
|
||||
<Col span={24} style={{display:'flex',flexDirection:'column'}}>
|
||||
<a style={{marginBottom: '10px',display:'flex'}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
||||
<Col span={24} style={{display:"flex", flexDirection: "column"}}>
|
||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signUpUrl}>
|
||||
<Button type="primary">{i18next.t("application:Test signup page..")}</Button>
|
||||
</a>
|
||||
<div style={{marginBottom:'10px', width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888" ,alignItems:'center',overflow:'auto',flexDirection:'column',flex:'auto'}}>
|
||||
<div style={{marginBottom:"10px", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
||||
{
|
||||
this.state.application.enablePassword ? (
|
||||
<SignupPage application={this.state.application} />
|
||||
@ -538,10 +563,10 @@ class ApplicationEditPage extends React.Component {
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<a style={{marginBottom: '10px',display:'flex'}} target="_blank" rel="noreferrer" href={signInUrl}>
|
||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={signInUrl}>
|
||||
<Button type="primary">{i18next.t("application:Test signin page..")}</Button>
|
||||
</a>
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888",alignItems:'center',overflow:'auto',flexDirection:'column',flex:'auto' }}>
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
||||
</div>
|
||||
</Col>
|
||||
@ -555,13 +580,13 @@ class ApplicationEditPage extends React.Component {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Col span={(Setting.isMobile()) ? 24 : 11} style={{display:'flex',flexDirection:'column',flex:'auto'}} >
|
||||
<a style={{marginBottom: '10px'}} target="_blank" rel="noreferrer" href={promptUrl}>
|
||||
<Col span={(Setting.isMobile()) ? 24 : 11} style={{display:"flex", flexDirection: "column", flex: "auto"}} >
|
||||
<a style={{marginBottom: "10px"}} target="_blank" rel="noreferrer" href={promptUrl}>
|
||||
<Button type="primary">{i18next.t("application:Test prompt page..")}</Button>
|
||||
</a>
|
||||
<br style={(Setting.isMobile()) ? {display:'none'} : {}} />
|
||||
<br style={(Setting.isMobile()) ? {display:'none'} : {}} />
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888",flexDirection:'column',flex:'auto'}}>
|
||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
||||
<br style={(Setting.isMobile()) ? {display: "none"} : {}} />
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
|
||||
<PromptPage application={this.state.application} account={this.props.account} />
|
||||
</div>
|
||||
</Col>
|
||||
|
@ -34,7 +34,7 @@ class ApplicationListPage extends BaseListPage {
|
||||
logo: "https://cdn.casdoor.com/logo/casdoor-logo_1185x256.png",
|
||||
enablePassword: true,
|
||||
enableSignUp: true,
|
||||
enableSigninSession: true,
|
||||
enableSigninSession: false,
|
||||
enableCodeSignin: false,
|
||||
providers: [],
|
||||
signupItems: [
|
||||
|
@ -17,3 +17,5 @@ export const GithubRepo = "https://github.com/casdoor/casdoor";
|
||||
|
||||
export const ForceLanguage = "";
|
||||
export const DefaultLanguage = "en";
|
||||
|
||||
export const EnableExtraPages = false;
|
||||
|
@ -113,12 +113,12 @@ class OrganizationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel("Favicon", i18next.t("general:Favicon - Tooltip"))} :
|
||||
{Setting.getLabel( i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
URL:
|
||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.organization.favicon} onChange={e => {
|
||||
@ -188,7 +188,7 @@ class OrganizationEditPage extends React.Component {
|
||||
<Col span={22} >
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
URL:
|
||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.organization.defaultAvatar} onChange={e => {
|
||||
@ -208,6 +208,18 @@ class OrganizationEditPage extends React.Component {
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("organization:Tags"), i18next.t("organization:Tags - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.organization.tags} onChange={(value => {this.updateOrganizationField('tags', value);})}>
|
||||
{
|
||||
this.state.organization.tags?.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Master password"), i18next.t("general:Master password - Tooltip"))} :
|
||||
|
@ -36,6 +36,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
PasswordSalt: "",
|
||||
phonePrefix: "86",
|
||||
defaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||
tags: [],
|
||||
masterPassword: "",
|
||||
enableSoftDeletion: false,
|
||||
}
|
||||
|
@ -13,15 +13,10 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
|
||||
import {Button, Card, Col, Input, Row} from 'antd';
|
||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import * as RoleBackend from "./backend/RoleBackend";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
class PaymentEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -105,16 +100,6 @@ class PaymentEditPage extends React.Component {
|
||||
}} />
|
||||
</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.payment.name} onChange={e => {
|
||||
// this.updatePaymentField('name', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Provider"), i18next.t("general:Provider - Tooltip"))} :
|
||||
@ -127,7 +112,7 @@ class PaymentEditPage extends React.Component {
|
||||
</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"))} :
|
||||
{Setting.getLabel(i18next.t("payment:Type"), i18next.t("payment:Type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.payment.type} onChange={e => {
|
||||
@ -137,20 +122,20 @@ class PaymentEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("payment:Good"), i18next.t("payment:Good - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("payment:Product"), i18next.t("payment:Product - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.payment.good} onChange={e => {
|
||||
// this.updatePaymentField('good', e.target.value);
|
||||
<Input value={this.state.payment.productName} onChange={e => {
|
||||
// this.updatePaymentField('productName', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("payment:Amount"), i18next.t("payment:Amount - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("payment:Price"), i18next.t("payment:Price - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.payment.amount} onChange={e => {
|
||||
<Input value={this.state.payment.price} onChange={e => {
|
||||
// this.updatePaymentField('amount', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
@ -165,6 +150,26 @@ class PaymentEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("payment:State"), i18next.t("payment:State - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.payment.state} onChange={e => {
|
||||
// this.updatePaymentField('state', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("payment:Message"), i18next.t("payment:Message - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.payment.message} onChange={e => {
|
||||
// this.updatePaymentField('message', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Popconfirm, Switch, Table} from 'antd';
|
||||
import {Button, Popconfirm, Table} from 'antd';
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||
@ -34,10 +34,16 @@ class PaymentListPage extends BaseListPage {
|
||||
type: "PayPal",
|
||||
organization: "built-in",
|
||||
user: "admin",
|
||||
good: "A notebook computer",
|
||||
amount: "300",
|
||||
productName: "computer-1",
|
||||
productDisplayName: "A notebook computer",
|
||||
detail: "This is a computer with excellent CPU, memory and disk",
|
||||
tag: "Promotion-1",
|
||||
currency: "USD",
|
||||
price: 300.00,
|
||||
payUrl: "https://pay.com/pay.php",
|
||||
returnUrl: "https://door.casdoor.com/payments",
|
||||
state: "Paid",
|
||||
message: "",
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,11 +78,11 @@ class PaymentListPage extends BaseListPage {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: 'owner',
|
||||
key: 'owner',
|
||||
dataIndex: 'organization',
|
||||
key: 'organization',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('owner'),
|
||||
...this.getColumnSearchProps('organization'),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@ -104,7 +110,7 @@ class PaymentListPage extends BaseListPage {
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '150px',
|
||||
width: '180px',
|
||||
fixed: 'left',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('name'),
|
||||
@ -151,10 +157,10 @@ class PaymentListPage extends BaseListPage {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:Type"),
|
||||
title: i18next.t("payment:Type"),
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: '110px',
|
||||
width: '140px',
|
||||
align: 'center',
|
||||
filterMultiple: false,
|
||||
filters: Setting.getProviderTypeOptions('Payment').map((o) => {return {text:o.id, value:o.name}}),
|
||||
@ -165,20 +171,20 @@ class PaymentListPage extends BaseListPage {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("payment:Good"),
|
||||
dataIndex: 'good',
|
||||
key: 'good',
|
||||
width: '160px',
|
||||
title: i18next.t("payment:Product"),
|
||||
dataIndex: 'productDisplayName',
|
||||
key: 'productDisplayName',
|
||||
// width: '160px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('good'),
|
||||
...this.getColumnSearchProps('productDisplayName'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("payment:Amount"),
|
||||
dataIndex: 'amount',
|
||||
key: 'amount',
|
||||
title: i18next.t("payment:Price"),
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('amount'),
|
||||
...this.getColumnSearchProps('price'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("payment:Currency"),
|
||||
@ -188,15 +194,24 @@ class PaymentListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('currency'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("payment:State"),
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('state'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '170px',
|
||||
width: '240px',
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/payments/${record.name}/result`)}>{i18next.t("payment:Result")}</Button>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/payments/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete payment: ${record.name} ?`}
|
||||
|
115
web/src/PaymentResultPage.js
Normal file
115
web/src/PaymentResultPage.js
Normal file
@ -0,0 +1,115 @@
|
||||
// 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.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Result, Spin} from 'antd';
|
||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
class PaymentResultPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
paymentName: props.match.params.paymentName,
|
||||
payment: null,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getPayment();
|
||||
}
|
||||
|
||||
getPayment() {
|
||||
PaymentBackend.getPayment("admin", this.state.paymentName)
|
||||
.then((payment) => {
|
||||
this.setState({
|
||||
payment: payment,
|
||||
});
|
||||
|
||||
if (payment.state === "Created") {
|
||||
setTimeout(() => this.getPayment(), 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const payment = this.state.payment;
|
||||
|
||||
if (payment === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (payment.state === "Paid") {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
<Result
|
||||
status="success"
|
||||
title={`${i18next.t("payment:You have successfully completed the payment")}: ${payment.productDisplayName}`}
|
||||
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
|
||||
extra={[
|
||||
<Button type="primary" key="returnUrl" onClick={() => {
|
||||
Setting.goToLink(payment.returnUrl);
|
||||
}}>
|
||||
{i18next.t("payment:Return to Website")}
|
||||
</Button>
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else if (payment.state === "Created") {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
<Result
|
||||
status="info"
|
||||
title={`${i18next.t("payment:The payment is still under processing")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}, ${i18next.t("payment:please wait for a few seconds...")}`}
|
||||
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
|
||||
extra={[
|
||||
<Spin size="large" tip={i18next.t("payment:Processing...")} />,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
<Result
|
||||
status="error"
|
||||
title={`${i18next.t("payment:The payment has failed")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}`}
|
||||
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
|
||||
extra={[
|
||||
<Button type="primary" key="returnUrl" onClick={() => {
|
||||
Setting.goToLink(payment.returnUrl);
|
||||
}}>
|
||||
{i18next.t("payment:Return to Website")}
|
||||
</Button>
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PaymentResultPage;
|
227
web/src/ProductBuyPage.js
Normal file
227
web/src/ProductBuyPage.js
Normal file
@ -0,0 +1,227 @@
|
||||
// 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.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Descriptions, Spin} from "antd";
|
||||
import i18next from "i18next";
|
||||
import * as ProductBackend from "./backend/ProductBackend";
|
||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import * as Provider from "./auth/Provider";
|
||||
import * as Setting from "./Setting";
|
||||
|
||||
class ProductBuyPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
productName: props.match?.params.productName,
|
||||
product: null,
|
||||
providers: [],
|
||||
isPlacingOrder: false,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getProduct();
|
||||
this.getPaymentProviders();
|
||||
}
|
||||
|
||||
getProduct() {
|
||||
ProductBackend.getProduct("admin", this.state.productName)
|
||||
.then((product) => {
|
||||
this.setState({
|
||||
product: product,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getPaymentProviders() {
|
||||
ProviderBackend.getProviders("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
providers: res.filter(provider => provider.category === "Payment"),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getProductObj() {
|
||||
if (this.props.product !== undefined) {
|
||||
return this.props.product;
|
||||
} else {
|
||||
return this.state.product;
|
||||
}
|
||||
}
|
||||
|
||||
getCurrencySymbol(product) {
|
||||
if (product?.currency === "USD") {
|
||||
return "$";
|
||||
} else if (product?.currency === "CNY") {
|
||||
return "¥";
|
||||
} else {
|
||||
return "(Unknown currency)";
|
||||
}
|
||||
}
|
||||
|
||||
getCurrencyText(product) {
|
||||
if (product?.currency === "USD") {
|
||||
return i18next.t("product:USD");
|
||||
} else if (product?.currency === "CNY") {
|
||||
return i18next.t("product:CNY");
|
||||
} else {
|
||||
return "(Unknown currency)";
|
||||
}
|
||||
}
|
||||
|
||||
getProviders(product) {
|
||||
if (this.state.providers.length === 0 || product.providers.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let providerMap = {};
|
||||
this.state.providers.forEach(provider => {
|
||||
providerMap[provider.name] = provider;
|
||||
})
|
||||
|
||||
return product.providers.map(providerName => providerMap[providerName]);
|
||||
}
|
||||
|
||||
getPayUrl(product, provider) {
|
||||
if (product === null || provider === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return `https://${provider.type}`;
|
||||
// if (provider.type === "WeChat") {
|
||||
// return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
||||
// } else if (provider.type === "GitHub") {
|
||||
// return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
||||
// }
|
||||
}
|
||||
|
||||
buyProduct(product, provider) {
|
||||
this.setState({
|
||||
isPlacingOrder: true,
|
||||
});
|
||||
|
||||
ProductBackend.buyProduct(this.state.product.owner, this.state.productName, provider.name)
|
||||
.then((res) => {
|
||||
if (res.msg === "") {
|
||||
const payUrl = res.data;
|
||||
Setting.goToLink(payUrl);
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
|
||||
this.setState({
|
||||
isPlacingOrder: false,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
getPayButton(provider) {
|
||||
let text = provider.type;
|
||||
if (provider.type === "Alipay") {
|
||||
text = i18next.t("product:Alipay");
|
||||
} else if (provider.type === "WeChat Pay") {
|
||||
text = i18next.t("product:WeChat Pay");
|
||||
} else if (provider.type === "Paypal") {
|
||||
text = i18next.t("product:Paypal");
|
||||
}
|
||||
|
||||
return (
|
||||
<Button style={{height: "50px", borderWidth: "2px"}} shape="round" icon={
|
||||
<img style={{marginRight: "10px"}} width={36} height={36} src={Provider.getProviderLogo(provider)} alt={provider.displayName} />
|
||||
} size={"large"} >
|
||||
{
|
||||
text
|
||||
}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
renderProviderButton(provider, product) {
|
||||
return (
|
||||
<span key={provider.name} style={{width: "200px", marginRight: "20px", marginBottom: "10px"}}>
|
||||
<span style={{width: "200px", cursor: "pointer"}} onClick={() => this.buyProduct(product, provider)}>
|
||||
{
|
||||
this.getPayButton(provider)
|
||||
}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
renderPay(product) {
|
||||
if (product === undefined || product === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (product.state !== "Published") {
|
||||
return i18next.t("product:This product is currently not in sale.");
|
||||
}
|
||||
if (product.providers.length === 0) {
|
||||
return i18next.t("product:There is no payment channel for this product.");
|
||||
}
|
||||
|
||||
const providers = this.getProviders(product);
|
||||
return providers.map(provider => {
|
||||
return this.renderProviderButton(provider, product);
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const product = this.getProductObj();
|
||||
|
||||
if (product === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
|
||||
<Descriptions title={i18next.t("product:Buy Product")} bordered>
|
||||
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
|
||||
<span style={{fontSize: 28}}>
|
||||
{product?.displayName}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:Detail")}><span style={{fontSize: 16}}>{product?.detail}</span></Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:Image")} span={3}>
|
||||
<img src={product?.image} alt={product?.image} height={90} style={{marginBottom: '20px'}}/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:Price")}>
|
||||
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
|
||||
{`${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</span></Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:Sold")}><span style={{fontSize: 16}}>{product?.sold}</span></Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:Pay")} span={3}>
|
||||
{
|
||||
this.renderPay(product)
|
||||
}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Spin>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ProductBuyPage;
|
321
web/src/ProductEditPage.js
Normal file
321
web/src/ProductEditPage.js
Normal file
@ -0,0 +1,321 @@
|
||||
// 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.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select} from 'antd';
|
||||
import * as ProductBackend from "./backend/ProductBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
class ProductEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||
productName: props.match.params.productName,
|
||||
product: null,
|
||||
providers: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getProduct();
|
||||
this.getPaymentProviders();
|
||||
}
|
||||
|
||||
getProduct() {
|
||||
ProductBackend.getProduct("admin", this.state.productName)
|
||||
.then((product) => {
|
||||
this.setState({
|
||||
product: product,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getPaymentProviders() {
|
||||
ProviderBackend.getProviders("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
providers: res.filter(provider => provider.category === "Payment"),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
parseProductField(key, value) {
|
||||
if ([""].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
updateProductField(key, value) {
|
||||
value = this.parseProductField(key, value);
|
||||
|
||||
let product = this.state.product;
|
||||
product[key] = value;
|
||||
this.setState({
|
||||
product: product,
|
||||
});
|
||||
}
|
||||
|
||||
renderProduct() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{this.state.mode === "add" ? i18next.t("product:New Product") : i18next.t("product:Edit Product")}
|
||||
<Button onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
||||
<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.product.name} onChange={e => {
|
||||
this.updateProductField('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.product.displayName} onChange={e => {
|
||||
this.updateProductField('displayName', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.product.image} onChange={e => {
|
||||
this.updateProductField('image', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
{i18next.t("general:Preview")}:
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<a target="_blank" rel="noreferrer" href={this.state.product.image}>
|
||||
<img src={this.state.product.image} alt={this.state.product.image} height={90} style={{marginBottom: '20px'}}/>
|
||||
</a>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Tag"), i18next.t("product:Tag - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.product.tag} onChange={e => {
|
||||
this.updateProductField('tag', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Detail"), i18next.t("product:Detail - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.product.detail} onChange={e => {
|
||||
this.updateProductField('detail', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Currency"), i18next.t("product:Currency - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.product.currency} onChange={(value => {
|
||||
this.updateProductField('currency', value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'USD', name: 'USD'},
|
||||
{id: 'CNY', name: 'CNY'},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Price"), i18next.t("product:Price - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.product.price} onChange={value => {
|
||||
this.updateProductField('price', value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Quantity"), i18next.t("product:Quantity - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.product.quantity} onChange={value => {
|
||||
this.updateProductField('quantity', value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Sold"), i18next.t("product:Sold - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.product.sold} onChange={value => {
|
||||
this.updateProductField('sold', value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Payment providers"), i18next.t("product:Payment providers - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.product.providers} onChange={(value => {this.updateProductField('providers', value);})}>
|
||||
{
|
||||
this.state.providers.map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Return URL"), i18next.t("product:Return URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.product.returnUrl} onChange={e => {
|
||||
this.updateProductField('returnUrl', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.product.state} onChange={(value => {
|
||||
this.updateProductField('state', value);
|
||||
})}>
|
||||
{
|
||||
[
|
||||
{id: 'Published', name: 'Published'},
|
||||
{id: 'Draft', name: 'Draft'},
|
||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
|
||||
</Col>
|
||||
{
|
||||
this.renderPreview()
|
||||
}
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
renderPreview() {
|
||||
let buyUrl = `/products/${this.state.product.name}/buy`;
|
||||
return (
|
||||
<Col span={22} style={{display: "flex", flexDirection: "column"}}>
|
||||
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={buyUrl}>
|
||||
<Button type="primary">{i18next.t("product:Test buy page..")}</Button>
|
||||
</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
||||
<ProductBuyPage product={this.state.product} />
|
||||
</div>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
submitProductEdit(willExist) {
|
||||
let product = Setting.deepCopy(this.state.product);
|
||||
ProductBackend.updateProduct(this.state.product.owner, this.state.productName, product)
|
||||
.then((res) => {
|
||||
if (res.msg === "") {
|
||||
Setting.showMessage("success", `Successfully saved`);
|
||||
this.setState({
|
||||
productName: this.state.product.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
this.props.history.push(`/products`);
|
||||
} else {
|
||||
this.props.history.push(`/products/${this.state.product.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
this.updateProductField('name', this.state.productName);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteProduct() {
|
||||
ProductBackend.deleteProduct(this.state.product)
|
||||
.then(() => {
|
||||
this.props.history.push(`/products`);
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Product failed to delete: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.state.product !== null ? this.renderProduct() : null
|
||||
}
|
||||
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||
<Button size="large" onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ProductEditPage;
|
296
web/src/ProductListPage.js
Normal file
296
web/src/ProductListPage.js
Normal file
@ -0,0 +1,296 @@
|
||||
// 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.
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from 'antd';
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as ProductBackend from "./backend/ProductBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import {EditOutlined} from "@ant-design/icons";
|
||||
|
||||
class ProductListPage extends BaseListPage {
|
||||
newProduct() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: "admin",
|
||||
name: `product_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
displayName: `New Product - ${randomName}`,
|
||||
image: "https://cdn.casdoor.com/logo/casdoor-logo_1185x256.png",
|
||||
tag: "Casdoor Summit 2022",
|
||||
currency: "USD",
|
||||
price: 300,
|
||||
quantity: 99,
|
||||
sold: 10,
|
||||
providers: [],
|
||||
state: "Published",
|
||||
}
|
||||
}
|
||||
|
||||
addProduct() {
|
||||
const newProduct = this.newProduct();
|
||||
ProductBackend.addProduct(newProduct)
|
||||
.then((res) => {
|
||||
this.props.history.push({pathname: `/products/${newProduct.name}`, mode: "add"});
|
||||
}
|
||||
)
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Product failed to add: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteProduct(i) {
|
||||
ProductBackend.deleteProduct(this.state.data[i])
|
||||
.then((res) => {
|
||||
Setting.showMessage("success", `Product deleted successfully`);
|
||||
this.setState({
|
||||
data: Setting.deleteRow(this.state.data, i),
|
||||
pagination: {total: this.state.pagination.total - 1},
|
||||
});
|
||||
}
|
||||
)
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `Product failed to delete: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderTable(products) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '140px',
|
||||
fixed: 'left',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('name'),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/products/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '160px',
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Display name"),
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
width: '170px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('displayName'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("product:Image"),
|
||||
dataIndex: 'image',
|
||||
key: 'image',
|
||||
width: '170px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<a target="_blank" rel="noreferrer" href={text}>
|
||||
<img src={text} alt={text} width={150} />
|
||||
</a>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("product:Tag"),
|
||||
dataIndex: 'tag',
|
||||
key: 'tag',
|
||||
width: '160px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('tag'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("product:Currency"),
|
||||
dataIndex: 'currency',
|
||||
key: 'currency',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('currency'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("product:Price"),
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('price'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("product:Quantity"),
|
||||
dataIndex: 'quantity',
|
||||
key: 'quantity',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('quantity'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("product:Sold"),
|
||||
dataIndex: 'sold',
|
||||
key: 'sold',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('sold'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:State"),
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('state'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("product:Payment providers"),
|
||||
dataIndex: 'providers',
|
||||
key: 'providers',
|
||||
width: '500px',
|
||||
...this.getColumnSearchProps('providers'),
|
||||
render: (text, record, index) => {
|
||||
const providers = text;
|
||||
if (providers.length === 0) {
|
||||
return "(empty)";
|
||||
}
|
||||
|
||||
const half = Math.floor((providers.length + 1) / 2);
|
||||
|
||||
const getList = (providers) => {
|
||||
return (
|
||||
<List
|
||||
size="small"
|
||||
locale={{emptyText: " "}}
|
||||
dataSource={providers}
|
||||
renderItem={(providerName, i) => {
|
||||
return (
|
||||
<List.Item>
|
||||
<div style={{display: "inline"}}>
|
||||
<Tooltip placement="topLeft" title="Edit">
|
||||
<Button style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => Setting.goToLinkSoft(this, `/providers/${providerName}`)} />
|
||||
</Tooltip>
|
||||
<Link to={`/providers/${providerName}`}>
|
||||
{providerName}
|
||||
</Link>
|
||||
</div>
|
||||
</List.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
{
|
||||
getList(providers.slice(0, half))
|
||||
}
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
{
|
||||
getList(providers.slice(half))
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '230px',
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/products/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/products/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete product: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteProduct(index)}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</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={products} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Products")}
|
||||
<Button type="primary" size="small" onClick={this.addProduct.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;
|
||||
let sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
if (params.type !== undefined && params.type !== null) {
|
||||
field = "type";
|
||||
value = params.type;
|
||||
}
|
||||
this.setState({ loading: true });
|
||||
ProductBackend.getProducts("", 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,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ProductListPage;
|
@ -72,6 +72,8 @@ class ProviderEditPage extends React.Component {
|
||||
case "SMS":
|
||||
if (this.state.provider.type === "Volc Engine SMS")
|
||||
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
|
||||
if (this.state.provider.type === "Huawei Cloud SMS")
|
||||
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
|
||||
default:
|
||||
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||
}
|
||||
@ -84,6 +86,8 @@ class ProviderEditPage extends React.Component {
|
||||
case "SMS":
|
||||
if (this.state.provider.type === "Volc Engine SMS")
|
||||
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
|
||||
if (this.state.provider.type === "Huawei Cloud SMS")
|
||||
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
|
||||
default:
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
@ -103,6 +107,9 @@ class ProviderEditPage extends React.Component {
|
||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
|
||||
text = i18next.t("provider:SMS account");
|
||||
tooltip = i18next.t("provider:SMS account - Tooltip");
|
||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Huawei Cloud SMS") {
|
||||
text = i18next.t("provider:Channel No.");
|
||||
tooltip = i18next.t("provider:Channel No. - Tooltip");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -296,7 +303,7 @@ class ProviderEditPage extends React.Component {
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.provider.type !== "Adfs" ? null : (
|
||||
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" ? null : (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||
|
@ -70,15 +70,7 @@ export function isProviderVisible(providerItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (providerItem.provider.type === "GitHub") {
|
||||
if (isLocalhost()) {
|
||||
return providerItem.provider.name.includes("localhost");
|
||||
} else {
|
||||
return !providerItem.provider.name.includes("localhost");
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isProviderVisibleForSignUp(providerItem) {
|
||||
@ -325,6 +317,10 @@ export function getAvatarColor(s) {
|
||||
return colorList[random % 4];
|
||||
}
|
||||
|
||||
export function getLanguage() {
|
||||
return i18next.language;
|
||||
}
|
||||
|
||||
export function setLanguage(language) {
|
||||
localStorage.setItem("language", language);
|
||||
changeMomentLanguage(language);
|
||||
@ -400,6 +396,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: 'GitLab', name: 'GitLab'},
|
||||
{id: 'Adfs', name: 'Adfs'},
|
||||
{id: 'Baidu', name: 'Baidu'},
|
||||
{id: 'Casdoor', name: 'Casdoor'},
|
||||
{id: 'Infoflow', name: 'Infoflow'},
|
||||
{id: 'Apple', name: 'Apple'},
|
||||
{id: 'AzureAD', name: 'AzureAD'},
|
||||
@ -419,6 +416,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: 'Aliyun SMS', name: 'Aliyun SMS'},
|
||||
{id: 'Tencent Cloud SMS', name: 'Tencent Cloud SMS'},
|
||||
{id: 'Volc Engine SMS', name: 'Volc Engine SMS'},
|
||||
{id: 'Huawei Cloud SMS', name: 'Huawei Cloud SMS'},
|
||||
]
|
||||
);
|
||||
} else if (category === "Storage") {
|
||||
@ -440,6 +438,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: 'Alipay', name: 'Alipay'},
|
||||
{id: 'WeChat Pay', name: 'WeChat Pay'},
|
||||
{id: 'PayPal', name: 'PayPal'},
|
||||
{id: 'GC', name: 'GC'},
|
||||
]);
|
||||
} else {
|
||||
return [];
|
||||
|
@ -171,6 +171,7 @@ class SignupTable extends React.Component {
|
||||
options = [
|
||||
{id: 'None', name: 'None'},
|
||||
{id: 'Real name', name: 'Real name'},
|
||||
{id: 'First, last', name: 'First, last'},
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -301,9 +301,24 @@ class UserEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.tag} onChange={e => {
|
||||
this.updateUserField('tag', e.target.value);
|
||||
}} />
|
||||
{
|
||||
this.state.application?.organizationObj.tags?.length > 0 ? (
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.user.tag} onChange={(value => {this.updateUserField('tag', value);})}>
|
||||
{
|
||||
this.state.application.organizationObj.tags?.map((tag, index) => {
|
||||
const tokens = tag.split("|");
|
||||
const value = tokens[0];
|
||||
const displayValue = Setting.getLanguage() !== "zh" ? tokens[0] : tokens[1];
|
||||
return <Option key={index} value={value}>{displayValue}</Option>
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
) : (
|
||||
<Input value={this.state.user.tag} onChange={e => {
|
||||
this.updateUserField('tag', e.target.value);
|
||||
}} />
|
||||
)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
|
@ -18,7 +18,7 @@ import UserEditPage from "../UserEditPage";
|
||||
class AccountPage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<UserEditPage organizationName={this.props.account.owner} userName={this.props.account.name} account={this.props.account} />
|
||||
<UserEditPage organizationName={this.props.account.owner} userName={this.props.account.name} account={this.props.account} location={this.props.location} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,10 @@ class AuthCallback extends React.Component {
|
||||
if (authServerUrl === realRedirectUrl) {
|
||||
return "login";
|
||||
} else {
|
||||
const responseType = innerParams.get("response_type");
|
||||
if (responseType !== null) {
|
||||
return responseType
|
||||
}
|
||||
return "code";
|
||||
}
|
||||
} else if (method === "link") {
|
||||
@ -102,6 +106,7 @@ class AuthCallback extends React.Component {
|
||||
method: method,
|
||||
};
|
||||
const oAuthParams = Util.getOAuthGetParameters(innerParams);
|
||||
const concatChar = oAuthParams?.redirectUri?.includes('?') ? '&' : '?';
|
||||
AuthBackend.login(body, oAuthParams)
|
||||
.then((res) => {
|
||||
if (res.status === 'ok') {
|
||||
@ -114,8 +119,11 @@ class AuthCallback extends React.Component {
|
||||
Setting.goToLink(link);
|
||||
} else if (responseType === "code") {
|
||||
const code = res.data;
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
||||
// Util.showMessage("success", `Authorization code: ${res.data}`);
|
||||
} else if (responseType === "token" || responseType === "id_token"){
|
||||
const token = res.data;
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
|
||||
} else if (responseType === "link") {
|
||||
const from = innerParams.get("from");
|
||||
Setting.goToLinkSoft(this, from);
|
||||
|
32
web/src/auth/CasdoorLoginButton.js
Normal file
32
web/src/auth/CasdoorLoginButton.js
Normal file
@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
import {createButton} from "react-social-login-buttons";
|
||||
import {StaticBaseUrl} from "../Setting";
|
||||
|
||||
function Icon({ width = 24, height = 24, color }) {
|
||||
return <img src={`${StaticBaseUrl}/buttons/casdoor.svg`} alt="Sign in with Casdoor" style={{width: 24, height: 24}} />;
|
||||
}
|
||||
|
||||
const config = {
|
||||
text: "Sign in with Casdoor",
|
||||
icon: Icon,
|
||||
iconFormat: name => `fa fa-${name}`,
|
||||
style: {background: "#ffffff", color: "#000000"},
|
||||
activeStyle: {background: "#ededee"},
|
||||
};
|
||||
|
||||
const CasdoorLoginButton = createButton(config);
|
||||
|
||||
export default CasdoorLoginButton;
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Col, Form, Select, Input, Row, Steps} from "antd";
|
||||
import {Button, Col, Form, Input, Row, Select, Steps} from "antd";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
import * as Util from "./Util";
|
||||
@ -43,6 +43,7 @@ class ForgetPage extends React.Component {
|
||||
msg: null,
|
||||
userId: "",
|
||||
username: "",
|
||||
name: "",
|
||||
email: "",
|
||||
isFixed: false,
|
||||
fixedContent: "",
|
||||
@ -100,7 +101,7 @@ class ForgetPage extends React.Component {
|
||||
if (res.status === "ok") {
|
||||
const phone = res.data.phone;
|
||||
const email = res.data.email;
|
||||
this.setState({phone: phone, email: email, username: res.data.name});
|
||||
this.setState({phone: phone, email: email, username: res.data.name, name: res.data.name});
|
||||
|
||||
if (phone !== "" && email === "") {
|
||||
this.setState({
|
||||
@ -134,15 +135,16 @@ class ForgetPage extends React.Component {
|
||||
break;
|
||||
case "step2":
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
if(this.state.verifyType=="email"){
|
||||
if (this.state.verifyType === "email") {
|
||||
this.setState({username: this.state.email})
|
||||
}else if(this.state.verifyType=="phone"){
|
||||
} else if (this.state.verifyType === "phone") {
|
||||
this.setState({username: this.state.phone})
|
||||
}
|
||||
AuthBackend.login({
|
||||
application: forms.step2.getFieldValue("application"),
|
||||
organization: forms.step2.getFieldValue("organization"),
|
||||
username: this.state.username,
|
||||
name: this.state.name,
|
||||
code: forms.step2.getFieldValue("emailCode"),
|
||||
phonePrefix: this.state.application?.organizationObj.phonePrefix,
|
||||
type: "login"
|
||||
@ -179,7 +181,7 @@ class ForgetPage extends React.Component {
|
||||
if (this.state.phone !== "") {
|
||||
options.push(
|
||||
<Option key={"phone"} value={"phone"}>
|
||||
{Setting.getMaskedPhone(this.state.phone)}
|
||||
{this.state.phone}
|
||||
</Option>
|
||||
);
|
||||
}
|
||||
@ -187,7 +189,7 @@ class ForgetPage extends React.Component {
|
||||
if (this.state.email !== "") {
|
||||
options.push(
|
||||
<Option key={"email"} value={"email"}>
|
||||
{Setting.getMaskedEmail(this.state.email)}
|
||||
{this.state.email}
|
||||
</Option>
|
||||
);
|
||||
}
|
||||
@ -349,12 +351,12 @@ class ForgetPage extends React.Component {
|
||||
{this.state.verifyType === "email" ? (
|
||||
<CountDownInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationOrgName(this.state.application)]}
|
||||
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationOrgName(this.state.application), this.state.name]}
|
||||
/>
|
||||
) : (
|
||||
<CountDownInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationOrgName(this.state.application)]}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationOrgName(this.state.application), this.state.name]}
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
@ -36,6 +36,7 @@ import LarkLoginButton from "./LarkLoginButton";
|
||||
import GitLabLoginButton from "./GitLabLoginButton";
|
||||
import AdfsLoginButton from "./AdfsLoginButton";
|
||||
import BaiduLoginButton from "./BaiduLoginButton";
|
||||
import CasdoorLoginButton from "./CasdoorLoginButton";
|
||||
import InfoflowLoginButton from "./InfoflowLoginButton";
|
||||
import AppleLoginButton from "./AppleLoginButton"
|
||||
import AzureADLoginButton from "./AzureADLoginButton";
|
||||
@ -56,7 +57,9 @@ class LoginPage extends React.Component {
|
||||
isCodeSignin: false,
|
||||
msg: null,
|
||||
username: null,
|
||||
validEmailOrPhone: false
|
||||
validEmailOrPhone: false,
|
||||
validEmail: false,
|
||||
validPhone: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -116,14 +119,18 @@ class LoginPage extends React.Component {
|
||||
onFinish(values) {
|
||||
const application = this.getApplicationObj();
|
||||
const ths = this;
|
||||
values["type"] = this.state.type;
|
||||
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
|
||||
if (oAuthParams !== null && oAuthParams.responseType!= null && oAuthParams.responseType !== "") {
|
||||
values["type"] = oAuthParams.responseType
|
||||
}else{
|
||||
values["type"] = this.state.type;
|
||||
}
|
||||
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
|
||||
|
||||
AuthBackend.login(values, oAuthParams)
|
||||
.then((res) => {
|
||||
if (res.status === 'ok') {
|
||||
const responseType = this.state.type;
|
||||
const responseType = values["type"];
|
||||
if (responseType === "login") {
|
||||
Util.showMessage("success", `Logged in successfully`);
|
||||
|
||||
@ -131,6 +138,7 @@ class LoginPage extends React.Component {
|
||||
Setting.goToLink(link);
|
||||
} else if (responseType === "code") {
|
||||
const code = res.data;
|
||||
const concatChar = oAuthParams?.redirectUri?.includes('?') ? '&' : '?';
|
||||
|
||||
if (Setting.hasPromptPage(application)) {
|
||||
AuthBackend.getAccount("")
|
||||
@ -143,7 +151,7 @@ class LoginPage extends React.Component {
|
||||
this.onUpdateAccount(account);
|
||||
|
||||
if (Setting.isPromptAnswered(account, application)) {
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
||||
} else {
|
||||
Setting.goToLinkSoft(ths, `/prompt/${application.name}?redirectUri=${oAuthParams.redirectUri}&code=${code}&state=${oAuthParams.state}`);
|
||||
}
|
||||
@ -152,10 +160,13 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
||||
}
|
||||
|
||||
// Util.showMessage("success", `Authorization code: ${res.data}`);
|
||||
} else if (responseType === "token" || responseType === "id_token") {
|
||||
const accessToken = res.data;
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
|
||||
}
|
||||
} else {
|
||||
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
||||
@ -191,6 +202,8 @@ class LoginPage extends React.Component {
|
||||
return <GitLabLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Adfs") {
|
||||
return <AdfsLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Casdoor") {
|
||||
return <CasdoorLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Baidu") {
|
||||
return <BaiduLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Infoflow") {
|
||||
@ -337,6 +350,12 @@ class LoginPage extends React.Component {
|
||||
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
|
||||
}
|
||||
}
|
||||
if (Setting.isValidPhone(this.state.username)) {
|
||||
this.setState({validPhone: true})
|
||||
}
|
||||
if (Setting.isValidEmail(this.state.username)) {
|
||||
this.setState({validEmail: true})
|
||||
}
|
||||
this.setState({validEmailOrPhone: true});
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -362,7 +381,7 @@ class LoginPage extends React.Component {
|
||||
>
|
||||
<CountDownInput
|
||||
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
|
||||
onButtonClickArgs={[this.state.username, "", Setting.getApplicationOrgName(application), true]}
|
||||
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationOrgName(application)]}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
@ -477,6 +496,7 @@ class LoginPage extends React.Component {
|
||||
<span style={{float: "right"}}>
|
||||
{i18next.t("login:No account?")}
|
||||
<a onClick={() => {
|
||||
sessionStorage.setItem("loginURL", window.location.href)
|
||||
Setting.goToSignup(this, application);
|
||||
}}>
|
||||
{i18next.t("login:sign up now")}
|
||||
|
@ -147,7 +147,10 @@ class PromptPage extends React.Component {
|
||||
if (res.status === 'ok') {
|
||||
this.onUpdateAccount(null);
|
||||
|
||||
const redirectUrl = this.getRedirectUrl();
|
||||
let redirectUrl = this.getRedirectUrl();
|
||||
if (redirectUrl === "") {
|
||||
redirectUrl = res.data2
|
||||
}
|
||||
if (redirectUrl !== "") {
|
||||
Setting.goToLink(redirectUrl);
|
||||
} else {
|
||||
|
@ -15,7 +15,7 @@
|
||||
import React from "react";
|
||||
import {Tooltip} from "antd";
|
||||
import * as Util from "./Util";
|
||||
import {StaticBaseUrl} from "../Setting";
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
const authInfo = {
|
||||
Google: {
|
||||
@ -78,6 +78,10 @@ const authInfo = {
|
||||
scope: "basic",
|
||||
endpoint: "http://openapi.baidu.com/oauth/2.0/authorize",
|
||||
},
|
||||
Casdoor: {
|
||||
scope: "openid%20profile%20email",
|
||||
endpoint: "http://example.com",
|
||||
},
|
||||
Infoflow: {
|
||||
endpoint: "https://xpc.im.baidu.com/oauth2/authorize",
|
||||
},
|
||||
@ -101,71 +105,79 @@ const authInfo = {
|
||||
const otherProviderInfo = {
|
||||
SMS: {
|
||||
"Aliyun SMS": {
|
||||
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_aliyun.png`,
|
||||
url: "https://aliyun.com/product/sms",
|
||||
},
|
||||
"Tencent Cloud SMS": {
|
||||
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_tencent_cloud.jpg`,
|
||||
url: "https://cloud.tencent.com/product/sms",
|
||||
},
|
||||
"Volc Engine SMS": {
|
||||
logo: `${StaticBaseUrl}/img/social_volc_engine.jpg`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_volc_engine.jpg`,
|
||||
url: "https://www.volcengine.com/products/cloud-sms",
|
||||
},
|
||||
"Huawei Cloud SMS": {
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_huawei.png`,
|
||||
url: "https://www.huaweicloud.com/product/msgsms.html",
|
||||
},
|
||||
},
|
||||
Email: {
|
||||
"Default": {
|
||||
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_default.png`,
|
||||
url: "",
|
||||
},
|
||||
},
|
||||
Storage: {
|
||||
"Local File System": {
|
||||
logo: `${StaticBaseUrl}/img/social_file.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_file.png`,
|
||||
url: "",
|
||||
},
|
||||
"AWS S3": {
|
||||
logo: `${StaticBaseUrl}/img/social_aws.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_aws.png`,
|
||||
url: "https://aws.amazon.com/s3",
|
||||
},
|
||||
"Aliyun OSS": {
|
||||
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_aliyun.png`,
|
||||
url: "https://aliyun.com/product/oss",
|
||||
},
|
||||
"Tencent Cloud COS": {
|
||||
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_tencent_cloud.jpg`,
|
||||
url: "https://cloud.tencent.com/product/cos",
|
||||
},
|
||||
},
|
||||
SAML: {
|
||||
"Aliyun IDaaS": {
|
||||
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_aliyun.png`,
|
||||
url: "https://aliyun.com/product/idaas"
|
||||
},
|
||||
"Keycloak": {
|
||||
logo: `${StaticBaseUrl}/img/social_keycloak.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/social_keycloak.png`,
|
||||
url: "https://www.keycloak.org/"
|
||||
},
|
||||
},
|
||||
Payment: {
|
||||
"Alipay": {
|
||||
logo: `${StaticBaseUrl}/img/payment_alipay.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/payment_alipay.png`,
|
||||
url: "https://www.alipay.com/"
|
||||
},
|
||||
"WeChat Pay": {
|
||||
logo: `${StaticBaseUrl}/img/payment_wechat_pay.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/payment_wechat_pay.png`,
|
||||
url: "https://pay.weixin.qq.com/"
|
||||
},
|
||||
"PayPal": {
|
||||
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
|
||||
logo: `${Setting.StaticBaseUrl}/img/payment_paypal.png`,
|
||||
url: "https://www.paypal.com/"
|
||||
},
|
||||
"GC": {
|
||||
logo: `${Setting.StaticBaseUrl}/img/payment_gc.png`,
|
||||
url: "https://gc.org"
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function getProviderLogo(provider) {
|
||||
if (provider.category === "OAuth") {
|
||||
return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
|
||||
return `${Setting.StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
|
||||
} else {
|
||||
return otherProviderInfo[provider.category][provider.type].logo;
|
||||
}
|
||||
@ -275,6 +287,8 @@ export function getAuthUrl(application, provider, method) {
|
||||
return `${provider.domain}/adfs/oauth2/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&nonce=casdoor&scope=openid`;
|
||||
} else if (provider.type === "Baidu") {
|
||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&display=popup`;
|
||||
} else if (provider.type === "Casdoor") {
|
||||
return `${provider.domain}/login/oauth/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||
} else if (provider.type === "Infoflow"){
|
||||
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}?state=${state}`
|
||||
} else if (provider.type === "Apple") {
|
||||
|
@ -65,7 +65,12 @@ class ResultPage extends React.Component {
|
||||
subTitle={i18next.t("signup:Please click the below button to sign in")}
|
||||
extra={[
|
||||
<Button type="primary" key="login" onClick={() => {
|
||||
Setting.goToLogin(this, application);
|
||||
let linkInStorage = sessionStorage.getItem("loginURL")
|
||||
if (linkInStorage != "") {
|
||||
Setting.goToLink(linkInStorage)
|
||||
} else {
|
||||
Setting.goToLogin(this, application)
|
||||
}
|
||||
}}>
|
||||
{i18next.t("login:Sign In")}
|
||||
</Button>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user