mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-22 18:23:51 +08:00
Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
3cf1b990be | |||
2023795f3c | |||
8d13bf7e27 | |||
29aa379fb2 | |||
7a95b9c1d5 | |||
0fc0ba0c76 | |||
24459d852e | |||
e3f5bf93b2 | |||
879ca6a488 | |||
544cd40a08 | |||
99f7883c7d | |||
88b0fb6e52 | |||
fa9b49e25b | |||
cd76e9372e | |||
04b9e05244 | |||
a78b2de7b2 | |||
d0952ae908 | |||
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 |
@ -82,6 +82,14 @@ Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which fo
|
|||||||
username:password@tcp(database_ip:database_port)/
|
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
|
#### Run
|
||||||
|
|
||||||
Casdoor provides two run modes, the difference is binary size and user prompt.
|
Casdoor provides two run modes, the difference is binary size and user prompt.
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
package authz
|
package authz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/astaxie/beego"
|
|
||||||
"github.com/casbin/casbin/v2"
|
"github.com/casbin/casbin/v2"
|
||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
xormadapter "github.com/casbin/xorm-adapter/v2"
|
xormadapter "github.com/casbin/xorm-adapter/v2"
|
||||||
@ -28,8 +27,8 @@ var Enforcer *casbin.Enforcer
|
|||||||
func InitAuthz() {
|
func InitAuthz() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
|
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||||
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true)
|
a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -88,6 +87,10 @@ p, *, *, GET, /api/get-application, *, *
|
|||||||
p, *, *, GET, /api/get-user, *, *
|
p, *, *, GET, /api/get-user, *, *
|
||||||
p, *, *, GET, /api/get-user-application, *, *
|
p, *, *, GET, /api/get-user-application, *, *
|
||||||
p, *, *, GET, /api/get-resources, *, *
|
p, *, *, GET, /api/get-resources, *, *
|
||||||
|
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/unlink, *, *
|
||||||
p, *, *, POST, /api/set-password, *, *
|
p, *, *, POST, /api/set-password, *, *
|
||||||
p, *, *, POST, /api/send-verification-code, *, *
|
p, *, *, POST, /api/send-verification-code, *, *
|
||||||
@ -98,6 +101,7 @@ p, *, *, GET, /.well-known/openid-configuration, *, *
|
|||||||
p, *, *, *, /.well-known/jwks, *, *
|
p, *, *, *, /.well-known/jwks, *, *
|
||||||
p, *, *, GET, /api/get-saml-login, *, *
|
p, *, *, GET, /api/get-saml-login, *, *
|
||||||
p, *, *, POST, /api/acs, *, *
|
p, *, *, POST, /api/acs, *, *
|
||||||
|
p, *, *, *, /cas, *, *
|
||||||
`
|
`
|
||||||
|
|
||||||
sa := stringadapter.NewAdapter(ruleText)
|
sa := stringadapter.NewAdapter(ruleText)
|
||||||
|
37
conf/conf.go
37
conf/conf.go
@ -15,14 +15,49 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"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 {
|
func GetBeegoConfDataSourceName() string {
|
||||||
dataSourceName := beego.AppConfig.String("dataSourceName")
|
dataSourceName := GetConfigString("dataSourceName")
|
||||||
|
|
||||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||||
if runningInDocker == "true" {
|
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,6 +18,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -28,6 +29,7 @@ const (
|
|||||||
ResponseTypeCode = "code"
|
ResponseTypeCode = "code"
|
||||||
ResponseTypeToken = "token"
|
ResponseTypeToken = "token"
|
||||||
ResponseTypeIdToken = "id_token"
|
ResponseTypeIdToken = "id_token"
|
||||||
|
ResponseTypeCas = "cas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestForm struct {
|
type RequestForm struct {
|
||||||
@ -130,8 +132,6 @@ func (c *ApiController) Signup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
|
|
||||||
|
|
||||||
id := util.GenerateId()
|
id := util.GenerateId()
|
||||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||||
lastUser := object.GetLastUser(form.Organization)
|
lastUser := object.GetLastUser(form.Organization)
|
||||||
@ -174,6 +174,13 @@ func (c *ApiController) Signup() {
|
|||||||
Karma: 0,
|
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 application.GetSignupItemRule("Display name") == "First, last" {
|
||||||
if form.FirstName != "" || form.LastName != "" {
|
if form.FirstName != "" || form.LastName != "" {
|
||||||
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
|
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
|
||||||
@ -203,6 +210,7 @@ func (c *ApiController) Signup() {
|
|||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
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)
|
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||||
|
|
||||||
c.ResponseOk(userId)
|
c.ResponseOk(userId)
|
||||||
@ -218,10 +226,15 @@ func (c *ApiController) Logout() {
|
|||||||
user := c.GetSessionUsername()
|
user := c.GetSessionUsername()
|
||||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||||
|
|
||||||
|
application := c.GetSessionApplication()
|
||||||
c.SetSessionUsername("")
|
c.SetSessionUsername("")
|
||||||
c.SetSessionData(nil)
|
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
|
// GetAccount
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -85,7 +86,7 @@ func (c *ApiController) GetUserApplication() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
user := object.GetUser(id)
|
user := object.GetUser(id)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError("No such user.")
|
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/idp"
|
"github.com/casdoor/casdoor/idp"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
@ -83,8 +83,25 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
resp = tokenToResponse(token)
|
resp = tokenToResponse(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if form.Type == ResponseTypeCas {
|
||||||
|
//not oauth but CAS SSO protocol
|
||||||
|
service := c.Input().Get("service")
|
||||||
|
resp = wrapErrorResponse(nil)
|
||||||
|
if service != "" {
|
||||||
|
st, err := object.GenerateCasToken(userId, service)
|
||||||
|
if err != nil {
|
||||||
|
resp = wrapErrorResponse(err)
|
||||||
|
} else {
|
||||||
|
resp.Data = st
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if application.EnableSigninSession || application.HasPromptPage() {
|
||||||
|
// The prompt page needs the user to be signed in
|
||||||
|
c.SetSessionUsername(userId)
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
|
resp = wrapErrorResponse(fmt.Errorf("Unknown response type: %s", form.Type))
|
||||||
}
|
}
|
||||||
|
|
||||||
// if user did not check auto signin
|
// if user did not check auto signin
|
||||||
@ -167,9 +184,16 @@ func (c *ApiController) Login() {
|
|||||||
var verificationCodeType string
|
var verificationCodeType string
|
||||||
var checkResult string
|
var checkResult string
|
||||||
|
|
||||||
|
if form.Name != "" {
|
||||||
|
user = object.GetUserByFields(form.Organization, form.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// check result through Email or Phone
|
// check result through Email or Phone
|
||||||
if strings.Contains(form.Username, "@") {
|
if strings.Contains(form.Username, "@") {
|
||||||
verificationCodeType = "email"
|
verificationCodeType = "email"
|
||||||
|
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
|
||||||
|
form.Username = user.Email
|
||||||
|
}
|
||||||
checkResult = object.CheckVerificationCode(form.Username, form.Code)
|
checkResult = object.CheckVerificationCode(form.Username, form.Code)
|
||||||
} else {
|
} else {
|
||||||
verificationCodeType = "phone"
|
verificationCodeType = "phone"
|
||||||
@ -178,6 +202,9 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(responseText)
|
c.ResponseError(responseText)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
|
||||||
|
form.Username = user.Phone
|
||||||
|
}
|
||||||
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
|
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
|
||||||
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
|
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
|
||||||
}
|
}
|
||||||
@ -192,7 +219,7 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
user = object.GetUserByFields(form.Organization, form.Username)
|
user = object.GetUserByFields(form.Organization, form.Username)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError("No such user.")
|
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -204,6 +231,11 @@ func (c *ApiController) Login() {
|
|||||||
resp = &Response{Status: "error", Msg: msg}
|
resp = &Response{Status: "error", Msg: msg}
|
||||||
} else {
|
} else {
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
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)
|
resp = c.HandleLoggedIn(application, user, &form)
|
||||||
|
|
||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
@ -213,6 +245,11 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
} else if form.Provider != "" {
|
} else if form.Provider != "" {
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
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))
|
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
|
||||||
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
|
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
|
||||||
providerItem := application.GetProviderItem(provider.Name)
|
providerItem := application.GetProviderItem(provider.Name)
|
||||||
@ -247,8 +284,8 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
setHttpClient(idProvider, provider.Type)
|
setHttpClient(idProvider, provider.Type)
|
||||||
|
|
||||||
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
|
if form.State != conf.GetConfigString("authState") && form.State != application.Name {
|
||||||
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State))
|
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", conf.GetConfigString("authState"), form.State))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,6 +420,11 @@ func (c *ApiController) Login() {
|
|||||||
if c.GetSessionUsername() != "" {
|
if c.GetSessionUsername() != "" {
|
||||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
resp = c.HandleLoggedIn(application, user, &form)
|
resp = c.HandleLoggedIn(application, user, &form)
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,6 +72,15 @@ func (c *ApiController) GetSessionUsername() string {
|
|||||||
return user.(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) {
|
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||||
sessionData := c.GetSessionData()
|
sessionData := c.GetSessionData()
|
||||||
if sessionData != nil &&
|
if sessionData != nil &&
|
||||||
@ -132,3 +141,11 @@ func wrapActionResponse(affected bool) *Response {
|
|||||||
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
|
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()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
204
controllers/cas.go
Normal file
204
controllers/cas.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
InvalidRequest string = "INVALID_REQUEST"
|
||||||
|
InvalidTicketSpec string = "INVALID_TICKET_SPEC"
|
||||||
|
UnauthorizedServiceProxy string = "UNAUTHORIZED_SERVICE_PROXY"
|
||||||
|
InvalidProxyCallback string = "INVALID_PROXY_CALLBACK"
|
||||||
|
InvalidTicket string = "INVALID_TICKET"
|
||||||
|
InvalidService string = "INVALID_SERVICE"
|
||||||
|
InteralError string = "INTERNAL_ERROR"
|
||||||
|
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *RootController) CasValidate() {
|
||||||
|
ticket := c.Input().Get("ticket")
|
||||||
|
service := c.Input().Get("service")
|
||||||
|
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
|
||||||
|
if service == "" || ticket == "" {
|
||||||
|
c.Ctx.Output.Body([]byte("no\n"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
|
||||||
|
//check whether service is the one for which we previously issued token
|
||||||
|
if issuedService == service {
|
||||||
|
c.Ctx.Output.Body([]byte(fmt.Sprintf("yes\n%s\n", response.User)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//token not found
|
||||||
|
c.Ctx.Output.Body([]byte("no\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RootController) CasServiceAndProxyValidate() {
|
||||||
|
ticket := c.Input().Get("ticket")
|
||||||
|
format := c.Input().Get("format")
|
||||||
|
service := c.Input().Get("service")
|
||||||
|
pgtUrl := c.Input().Get("pgtUrl")
|
||||||
|
|
||||||
|
serviceResponse := object.CasServiceResponse{
|
||||||
|
Xmlns: "http://www.yale.edu/tp/cas",
|
||||||
|
}
|
||||||
|
|
||||||
|
//check whether all required parameters are met
|
||||||
|
if service == "" || ticket == "" {
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidRequest, "service and ticket must exist", format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//find the token
|
||||||
|
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
|
||||||
|
|
||||||
|
//check whether service is the one for which we previously issued token
|
||||||
|
if strings.HasPrefix(service, issuedService) {
|
||||||
|
serviceResponse.Success = response
|
||||||
|
} else {
|
||||||
|
//service not match
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidService, fmt.Sprintf("service %s and %s does not match", service, issuedService), format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//token not found
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pgtUrl != "" && serviceResponse.Failure == nil {
|
||||||
|
//that means we are in proxy web flow
|
||||||
|
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service)
|
||||||
|
pgtiou := serviceResponse.Success.ProxyGrantingTicket
|
||||||
|
//todo: check whether it is https
|
||||||
|
pgtUrlObj, err := url.Parse(pgtUrl)
|
||||||
|
if pgtUrlObj.Scheme != "https" {
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//make a request to pgturl passing pgt and pgtiou
|
||||||
|
if err != nil {
|
||||||
|
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
param := pgtUrlObj.Query()
|
||||||
|
param.Add("pgtId", pgt)
|
||||||
|
param.Add("pgtIou", pgtiou)
|
||||||
|
pgtUrlObj.RawQuery = param.Encode()
|
||||||
|
|
||||||
|
request, err := http.NewRequest("GET", pgtUrlObj.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(request)
|
||||||
|
if err != nil || !(resp.StatusCode >= 200 && resp.StatusCode < 400) {
|
||||||
|
//failed to send request
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// everything is ok, send the response
|
||||||
|
if format == "json" {
|
||||||
|
c.Data["json"] = serviceResponse
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
c.Data["xml"] = serviceResponse
|
||||||
|
c.ServeXML()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RootController) CasProxy() {
|
||||||
|
pgt := c.Input().Get("pgt")
|
||||||
|
targetService := c.Input().Get("targetService")
|
||||||
|
format := c.Input().Get("format")
|
||||||
|
if pgt == "" || targetService == "" {
|
||||||
|
c.sendCasProxyResponseErr(InvalidRequest, "pgt and targetService must exist", format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, authenticationSuccess, issuedService := object.GetCasTokenByPgt(pgt)
|
||||||
|
if !ok {
|
||||||
|
c.sendCasProxyResponseErr(UnauthorizedService, "service not authorized", format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newAuthenticationSuccess := authenticationSuccess.DeepCopy()
|
||||||
|
if newAuthenticationSuccess.Proxies == nil {
|
||||||
|
newAuthenticationSuccess.Proxies = &object.CasProxies{}
|
||||||
|
}
|
||||||
|
newAuthenticationSuccess.Proxies.Proxies = append(newAuthenticationSuccess.Proxies.Proxies, issuedService)
|
||||||
|
proxyTicket := object.StoreCasTokenForProxyTicket(&newAuthenticationSuccess, targetService)
|
||||||
|
|
||||||
|
serviceResponse := object.CasServiceResponse{
|
||||||
|
Xmlns: "http://www.yale.edu/tp/cas",
|
||||||
|
ProxySuccess: &object.CasProxySuccess{
|
||||||
|
ProxyTicket: proxyTicket,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if format == "json" {
|
||||||
|
c.Data["json"] = serviceResponse
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
c.Data["xml"] = serviceResponse
|
||||||
|
c.ServeXML()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func (c *RootController) sendCasProxyResponseErr(code, msg, format string) {
|
||||||
|
serviceResponse := object.CasServiceResponse{
|
||||||
|
Xmlns: "http://www.yale.edu/tp/cas",
|
||||||
|
ProxyFailure: &object.CasProxyFailure{
|
||||||
|
Code: code,
|
||||||
|
Message: msg,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if format == "json" {
|
||||||
|
c.Data["json"] = serviceResponse
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
c.Data["xml"] = serviceResponse
|
||||||
|
c.ServeXML()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format string) {
|
||||||
|
serviceResponse := object.CasServiceResponse{
|
||||||
|
Xmlns: "http://www.yale.edu/tp/cas",
|
||||||
|
Failure: &object.CasAuthenticationFailure{
|
||||||
|
Code: code,
|
||||||
|
Message: msg,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if format == "json" {
|
||||||
|
c.Data["json"] = serviceResponse
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
c.Data["xml"] = serviceResponse
|
||||||
|
c.ServeXML()
|
||||||
|
}
|
||||||
|
}
|
@ -178,7 +178,7 @@ func (c *ApiController) UpdateLdap() {
|
|||||||
}
|
}
|
||||||
if ldap.AutoSync != 0 {
|
if ldap.AutoSync != 0 {
|
||||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
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)
|
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"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
|
// @Title GetPayment
|
||||||
// @Tag Payment API
|
// @Tag Payment API
|
||||||
// @Description get payment
|
// @Description get payment
|
||||||
@ -114,3 +133,28 @@ func (c *ApiController) DeletePayment() {
|
|||||||
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
|
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
|
||||||
c.ServeJSON()
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -114,3 +115,36 @@ func (c *ApiController) DeleteProduct() {
|
|||||||
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))
|
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))
|
||||||
c.ServeJSON()
|
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)
|
||||||
|
}
|
||||||
|
@ -179,6 +179,20 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
if clientId == "" && clientSecret == "" {
|
if clientId == "" && clientSecret == "" {
|
||||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||||
}
|
}
|
||||||
|
if clientId == "" {
|
||||||
|
// If clientID is empty, try to read data from RequestBody
|
||||||
|
var tokenRequest TokenRequest
|
||||||
|
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
|
||||||
|
clientId = tokenRequest.ClientId
|
||||||
|
clientSecret = tokenRequest.ClientSecret
|
||||||
|
grantType = tokenRequest.GrantType
|
||||||
|
code = tokenRequest.Code
|
||||||
|
verifier = tokenRequest.Verifier
|
||||||
|
scope = tokenRequest.Scope
|
||||||
|
username = tokenRequest.Username
|
||||||
|
password = tokenRequest.Password
|
||||||
|
}
|
||||||
|
}
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host)
|
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host)
|
||||||
@ -193,7 +207,7 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
// @Param refresh_token query string true "OAuth refresh token"
|
// @Param refresh_token query string true "OAuth refresh token"
|
||||||
// @Param scope query string true "OAuth scope"
|
// @Param scope query string true "OAuth scope"
|
||||||
// @Param client_id query string true "OAuth client id"
|
// @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
|
// @Success 200 {object} object.TokenWrapper The Response object
|
||||||
// @router /login/oauth/refresh_token [post]
|
// @router /login/oauth/refresh_token [post]
|
||||||
func (c *ApiController) RefreshToken() {
|
func (c *ApiController) RefreshToken() {
|
||||||
@ -204,6 +218,18 @@ func (c *ApiController) RefreshToken() {
|
|||||||
clientSecret := c.Input().Get("client_secret")
|
clientSecret := c.Input().Get("client_secret")
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
|
if clientId == "" {
|
||||||
|
// If clientID is empty, try to read data from RequestBody
|
||||||
|
var tokenRequest TokenRequest
|
||||||
|
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
|
||||||
|
clientId = tokenRequest.ClientId
|
||||||
|
clientSecret = tokenRequest.ClientSecret
|
||||||
|
grantType = tokenRequest.GrantType
|
||||||
|
scope = tokenRequest.Scope
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
26
controllers/types.go
Normal file
26
controllers/types.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
type TokenRequest struct {
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
Verifier string `json:"code_verifier"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
@ -190,19 +190,23 @@ func (c *ApiController) GetEmailAndPhone() {
|
|||||||
|
|
||||||
user := object.GetUserByFields(form.Organization, form.Username)
|
user := object.GetUserByFields(form.Organization, form.Username)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError("No such user.")
|
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name}
|
respUser := object.User{Name: user.Name}
|
||||||
var contentType string
|
var contentType string
|
||||||
switch form.Username {
|
switch form.Username {
|
||||||
case user.Email:
|
case user.Email:
|
||||||
contentType = "email"
|
contentType = "email"
|
||||||
|
respUser.Email = user.Email
|
||||||
case user.Phone:
|
case user.Phone:
|
||||||
contentType = "phone"
|
contentType = "phone"
|
||||||
|
respUser.Phone = user.Phone
|
||||||
case user.Name:
|
case user.Name:
|
||||||
contentType = "username"
|
contentType = "username"
|
||||||
|
respUser.Email = util.GetMaskedEmail(user.Email)
|
||||||
|
respUser.Phone = util.GetMaskedPhone(user.Phone)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(respUser, contentType)
|
c.ResponseOk(respUser, contentType)
|
||||||
@ -226,7 +230,7 @@ func (c *ApiController) SetPassword() {
|
|||||||
|
|
||||||
requestUserId := c.GetSessionUsername()
|
requestUserId := c.GetSessionUsername()
|
||||||
if requestUserId == "" {
|
if requestUserId == "" {
|
||||||
c.ResponseError("Please login first.")
|
c.ResponseError("Please login first")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@ -62,7 +62,7 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getInitScore() int {
|
func getInitScore() int {
|
||||||
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
|
score, err := strconv.Atoi(conf.GetConfigString("initScore"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -68,15 +68,22 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
organization := object.GetOrganization(orgId)
|
organization := object.GetOrganization(orgId)
|
||||||
application := object.GetApplicationByOrganizationName(organization.Name)
|
application := object.GetApplicationByOrganizationName(organization.Name)
|
||||||
|
|
||||||
if checkUser == "true" && user == nil &&
|
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
|
||||||
object.GetUserByFields(organization.Name, dest) == nil {
|
c.ResponseError("Please login first")
|
||||||
c.ResponseError("No such user.")
|
|
||||||
return
|
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 {
|
switch destType {
|
||||||
case "email":
|
case "email":
|
||||||
|
if user != nil && util.GetMaskedEmail(user.Email) == dest {
|
||||||
|
dest = user.Email
|
||||||
|
}
|
||||||
if !util.IsEmailValid(dest) {
|
if !util.IsEmailValid(dest) {
|
||||||
c.ResponseError("Invalid Email address")
|
c.ResponseError("Invalid Email address")
|
||||||
return
|
return
|
||||||
@ -85,6 +92,9 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
provider := application.GetEmailProvider()
|
provider := application.GetEmailProvider()
|
||||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||||
case "phone":
|
case "phone":
|
||||||
|
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||||
|
dest = user.Phone
|
||||||
|
}
|
||||||
if !util.IsPhoneCnValid(dest) {
|
if !util.IsPhoneCnValid(dest) {
|
||||||
c.ResponseError("Invalid phone number")
|
c.ResponseError("Invalid phone number")
|
||||||
return
|
return
|
||||||
@ -121,7 +131,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
|
|
||||||
user := object.GetUser(userId)
|
user := object.GetUser(userId)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError("No such user.")
|
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ services:
|
|||||||
- db
|
- db
|
||||||
environment:
|
environment:
|
||||||
RUNNING_IN_DOCKER: "true"
|
RUNNING_IN_DOCKER: "true"
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
volumes:
|
volumes:
|
||||||
- ./conf:/conf/
|
- ./conf:/conf/
|
||||||
db:
|
db:
|
||||||
|
6
go.mod
6
go.mod
@ -9,10 +9,11 @@ require (
|
|||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||||
github.com/casbin/casbin/v2 v2.30.1
|
github.com/casbin/casbin/v2 v2.30.1
|
||||||
github.com/casbin/xorm-adapter/v2 v2.5.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/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0
|
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/go-sql-driver/mysql v1.5.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.1.0
|
github.com/golang-jwt/jwt/v4 v4.1.0
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
@ -24,12 +25,11 @@ require (
|
|||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/gosaml2 v0.6.0
|
github.com/russellhaering/gosaml2 v0.6.0
|
||||||
github.com/russellhaering/goxmldsig v1.1.1
|
github.com/russellhaering/goxmldsig v1.1.1
|
||||||
github.com/satori/go.uuid v1.2.0 // indirect
|
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/tealeg/xlsx v1.0.5
|
github.com/tealeg/xlsx v1.0.5
|
||||||
github.com/thanhpk/randstr v1.0.4
|
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/net v0.0.0-20211112202133-69e39bad7dc2
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||||
|
11
go.sum
11
go.sum
@ -79,8 +79,8 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
|
|||||||
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||||
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
|
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
|
||||||
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
|
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.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
|
||||||
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
|
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/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 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
@ -124,6 +124,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-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.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
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-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.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
@ -377,10 +379,11 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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-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-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
@ -88,7 +88,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
292
idp/alipay.go
Normal file
292
idp/alipay.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
// 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 (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AlipayIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAlipayIdProvider ...
|
||||||
|
func NewAlipayIdProvider(clientId string, clientSecret string, redirectUrl string) *AlipayIdProvider {
|
||||||
|
idp := &AlipayIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
idp.Config = config
|
||||||
|
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHttpClient ...
|
||||||
|
func (idp *AlipayIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||||
|
func (idp *AlipayIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm",
|
||||||
|
TokenURL: "https://openapi.alipay.com/gateway.do",
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
Scopes: []string{"", ""},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlipayAccessToken struct {
|
||||||
|
Response AlipaySystemOauthTokenResponse `json:"alipay_system_oauth_token_response"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlipaySystemOauthTokenResponse struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
AlipayUserId string `json:"alipay_user_id"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
ReExpiresIn int `json:"re_expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
UserId string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code to get access_token
|
||||||
|
func (idp *AlipayIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
pTokenParams := &struct {
|
||||||
|
ClientId string `json:"app_id"`
|
||||||
|
CharSet string `json:"charset"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
SignType string `json:"sign_type"`
|
||||||
|
TimeStamp string `json:"timestamp"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}{idp.Config.ClientID, "utf-8", code, "authorization_code", "alipay.system.oauth.token", "RSA2", time.Now().Format("2006-01-02 15:04:05"), "1.0"}
|
||||||
|
|
||||||
|
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pToken := &AlipayAccessToken{}
|
||||||
|
err = json.Unmarshal(data, pToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: pToken.Response.AccessToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+int64(pToken.Response.ExpiresIn), 0),
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"alipay_user_info_share_response":{
|
||||||
|
"code":"10000",
|
||||||
|
"msg":"Success",
|
||||||
|
"avatar":"https:\/\/tfs.alipayobjects.com\/images\/partner\/T1.QxFXk4aXXXXXXXX",
|
||||||
|
"nick_name":"zhangsan",
|
||||||
|
"user_id":"2099222233334444"
|
||||||
|
},
|
||||||
|
"sign":"m8rWJeqfoa5tDQRRVnPhRHcpX7NZEgjIPTPF1QBxos6XXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type AlipayUserResponse struct {
|
||||||
|
AlipayUserInfoShareResponse AlipayUserInfoShareResponse `json:"alipay_user_info_share_response"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlipayUserInfoShareResponse struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
NickName string `json:"nick_name"`
|
||||||
|
UserId string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo Use access_token to get UserInfo
|
||||||
|
func (idp *AlipayIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
atUserInfo := &AlipayUserResponse{}
|
||||||
|
accessToken := token.AccessToken
|
||||||
|
|
||||||
|
pTokenParams := &struct {
|
||||||
|
ClientId string `json:"app_id"`
|
||||||
|
CharSet string `json:"charset"`
|
||||||
|
AuthToken string `json:"auth_token"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
SignType string `json:"sign_type"`
|
||||||
|
TimeStamp string `json:"timestamp"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}{idp.Config.ClientID, "utf-8", accessToken, "alipay.user.info.share", "RSA2", time.Now().Format("2006-01-02 15:04:05"), "1.0"}
|
||||||
|
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, atUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := UserInfo{
|
||||||
|
Id: atUserInfo.AlipayUserInfoShareResponse.UserId,
|
||||||
|
Username: atUserInfo.AlipayUserInfoShareResponse.NickName,
|
||||||
|
DisplayName: atUserInfo.AlipayUserInfoShareResponse.NickName,
|
||||||
|
AvatarUrl: atUserInfo.AlipayUserInfoShareResponse.Avatar,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([]byte, error) {
|
||||||
|
bs, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyJson := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(bs, &bodyJson)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
formData := url.Values{}
|
||||||
|
for k := range bodyJson {
|
||||||
|
formData.Set(k, bodyJson[k].(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
sign, err := rsaSignWithRSA256(getStringToSign(formData), idp.Config.ClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.Set("sign", sign)
|
||||||
|
|
||||||
|
resp, err := idp.Client.PostForm(targetUrl, formData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the string to sign, see https://opendocs.alipay.com/common/02kf5q
|
||||||
|
func getStringToSign(formData url.Values) string {
|
||||||
|
keys := make([]string, 0, len(formData))
|
||||||
|
for k := range formData {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
str := ""
|
||||||
|
for _, k := range keys {
|
||||||
|
if k == "sign" || formData[k][0] == "" {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
str += "&" + k + "=" + formData[k][0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str = strings.Trim(str, "&")
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// use privateKey to sign the content
|
||||||
|
func rsaSignWithRSA256(signContent string, privateKey string) (string, error) {
|
||||||
|
privateKey = formatPrivateKey(privateKey)
|
||||||
|
block, _ := pem.Decode([]byte(privateKey))
|
||||||
|
if block == nil {
|
||||||
|
panic("fail to parse privateKey")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(signContent))
|
||||||
|
hashed := h.Sum(nil)
|
||||||
|
|
||||||
|
privateKeyRSA, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKeyRSA.(*rsa.PrivateKey), crypto.SHA256, hashed)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(signature), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// privateKey in database is a string, format it to PEM style
|
||||||
|
func formatPrivateKey(privateKey string) string {
|
||||||
|
// each line length is 64
|
||||||
|
preFmtPrivateKey := ""
|
||||||
|
for i := 0; ; {
|
||||||
|
if i+64 <= len(privateKey) {
|
||||||
|
preFmtPrivateKey = preFmtPrivateKey + privateKey[i:i+64] + "\n"
|
||||||
|
i += 64
|
||||||
|
} else {
|
||||||
|
preFmtPrivateKey = preFmtPrivateKey + privateKey[i:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
privateKey = strings.Trim(preFmtPrivateKey, "\n")
|
||||||
|
|
||||||
|
// add pkcs#8 BEGIN and END
|
||||||
|
PemBegin := "-----BEGIN PRIVATE KEY-----\n"
|
||||||
|
PemEnd := "\n-----END PRIVATE KEY-----"
|
||||||
|
if !strings.HasPrefix(privateKey, PemBegin) {
|
||||||
|
privateKey = PemBegin + privateKey
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(privateKey, PemEnd) {
|
||||||
|
privateKey = privateKey + PemEnd
|
||||||
|
}
|
||||||
|
return privateKey
|
||||||
|
}
|
@ -18,7 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
@ -97,7 +97,7 @@ func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -143,7 +144,7 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -178,7 +179,7 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -92,7 +93,7 @@ func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rbs, err := io.ReadAll(resp.Body)
|
rbs, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,13 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
@ -60,9 +62,38 @@ func (idp *GithubIdProvider) getConfig() *oauth2.Config {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GithubToken struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
|
params := &struct {
|
||||||
return idp.Config.Exchange(ctx, code)
|
Code string `json:"code"`
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
}{code, idp.Config.ClientID, idp.Config.ClientSecret}
|
||||||
|
data, err := idp.postWithBody(params, idp.Config.Endpoint.TokenURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pToken := &GithubToken{}
|
||||||
|
if err = json.Unmarshal(data, pToken); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pToken.Error != "" {
|
||||||
|
return nil, fmt.Errorf("err: %s", pToken.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: pToken.AccessToken,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//{
|
//{
|
||||||
@ -172,7 +203,7 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -192,3 +223,30 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
}
|
}
|
||||||
return &userInfo, nil
|
return &userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (idp *GithubIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
|
||||||
|
bs, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := strings.NewReader(string(bs))
|
||||||
|
req, _ := http.NewRequest("POST", url, r)
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
resp, err := idp.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
@ -17,7 +17,7 @@ package idp
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -85,7 +85,7 @@ func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -209,7 +209,7 @@ func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
@ -95,7 +95,7 @@ func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -231,6 +231,10 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
value.Add("code", code)
|
value.Add("code", code)
|
||||||
}
|
}
|
||||||
accessToken, err := idp.Session.Authorize(idp.Provider, value)
|
accessToken, err := idp.Session.Authorize(idp.Provider, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
//Get ExpiresAt's value
|
//Get ExpiresAt's value
|
||||||
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
|
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
|
||||||
if valueOfExpire.IsValid() {
|
if valueOfExpire.IsValid() {
|
||||||
@ -240,7 +244,8 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
Expiry: expireAt,
|
Expiry: expireAt,
|
||||||
}
|
}
|
||||||
return &token, err
|
|
||||||
|
return &token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
@ -17,7 +17,7 @@ package idp
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
@ -69,7 +69,7 @@ func (idp *InfoflowInternalIdProvider) GetToken(code string) (*oauth2.Token, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -147,7 +147,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err = io.ReadAll(resp.Body)
|
data, err = ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -143,7 +144,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -161,7 +162,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err = io.ReadAll(resp.Body)
|
data, err = ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -196,7 +197,7 @@ func (idp *InfoflowIdProvider) postWithBody(body interface{}, url string) ([]byt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package idp
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -168,7 +169,7 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
||||||
|
|
||||||
resp, err := idp.Client.Do(req)
|
resp, err := idp.Client.Do(req)
|
||||||
data, err = io.ReadAll(resp.Body)
|
data, err = ioutil.ReadAll(resp.Body)
|
||||||
err = resp.Body.Close()
|
err = resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -200,7 +201,7 @@ func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@ -84,7 +85,7 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rbs, err := io.ReadAll(resp.Body)
|
rbs, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -322,7 +323,7 @@ func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([
|
|||||||
}
|
}
|
||||||
}(resp.Body)
|
}(resp.Body)
|
||||||
|
|
||||||
bs, err := io.ReadAll(resp.Body)
|
bs, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
} else if typ == "Baidu" {
|
} else if typ == "Baidu" {
|
||||||
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
|
} else if typ == "Alipay" {
|
||||||
|
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if typ == "Infoflow" {
|
} else if typ == "Infoflow" {
|
||||||
if subType == "Internal" {
|
if subType == "Internal" {
|
||||||
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
|
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
|
||||||
@ -78,6 +80,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
} else if typ == "Casdoor" {
|
||||||
|
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
} else if isGothSupport(typ) {
|
} else if isGothSupport(typ) {
|
||||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||||
}
|
}
|
||||||
|
15
idp/qq.go
15
idp/qq.go
@ -18,7 +18,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -75,7 +75,10 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
tokenContent, err := io.ReadAll(resp.Body)
|
tokenContent, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
re := regexp.MustCompile("token=(.*?)&")
|
re := regexp.MustCompile("token=(.*?)&")
|
||||||
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
|
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
|
||||||
@ -145,7 +148,10 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
openIdBody, err := io.ReadAll(resp.Body)
|
openIdBody, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
|
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
|
||||||
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
|
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
|
||||||
@ -161,7 +167,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
userInfoBody, err := io.ReadAll(resp.Body)
|
userInfoBody, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -178,6 +184,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
|
|
||||||
userInfo := UserInfo{
|
userInfo := UserInfo{
|
||||||
Id: openId,
|
Id: openId,
|
||||||
|
Username: qqUserInfo.Nickname,
|
||||||
DisplayName: qqUserInfo.Nickname,
|
DisplayName: qqUserInfo.Nickname,
|
||||||
AvatarUrl: qqUserInfo.FigureurlQq1,
|
AvatarUrl: qqUserInfo.FigureurlQq1,
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ package idp
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -111,6 +111,7 @@ type WecomInternalUserInfo struct {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
OpenId string `json:"open_userid"`
|
OpenId string `json:"open_userid"`
|
||||||
|
UserId string `json:"userid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
@ -122,7 +123,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -143,7 +144,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err = io.ReadAll(resp.Body)
|
data, err = ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -156,7 +157,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
|
|||||||
return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
|
return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
|
||||||
}
|
}
|
||||||
userInfo := UserInfo{
|
userInfo := UserInfo{
|
||||||
Id: infoResp.OpenId,
|
Id: infoResp.UserId,
|
||||||
Username: infoResp.Name,
|
Username: infoResp.Name,
|
||||||
DisplayName: infoResp.Name,
|
DisplayName: infoResp.Name,
|
||||||
Email: infoResp.Email,
|
Email: infoResp.Email,
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -194,7 +195,7 @@ func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -91,7 +92,7 @@ func (idp *WeiBoIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(resp.Body)
|
}(resp.Body)
|
||||||
bs, err := io.ReadAll(resp.Body)
|
bs, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
6
main.go
6
main.go
@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
_ "github.com/astaxie/beego/session/redis"
|
_ "github.com/astaxie/beego/session/redis"
|
||||||
"github.com/casdoor/casdoor/authz"
|
"github.com/casdoor/casdoor/authz"
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/routers"
|
"github.com/casdoor/casdoor/routers"
|
||||||
@ -31,6 +32,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
object.InitAdapter(*createDatabase)
|
object.InitAdapter(*createDatabase)
|
||||||
object.InitDb()
|
object.InitDb()
|
||||||
object.InitDefaultStorageProvider()
|
object.InitDefaultStorageProvider()
|
||||||
@ -52,12 +54,12 @@ func main() {
|
|||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||||
|
|
||||||
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
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.SessionProvider = "file"
|
||||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
|
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
|
||||||
} else {
|
} else {
|
||||||
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
|
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.SessionCookieLifeTime = 3600 * 24 * 30
|
||||||
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
||||||
|
@ -41,7 +41,7 @@ func InitConfig() {
|
|||||||
|
|
||||||
func InitAdapter(createDatabase bool) {
|
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 {
|
if createDatabase {
|
||||||
adapter.CreateDatabase()
|
adapter.CreateDatabase()
|
||||||
}
|
}
|
||||||
@ -111,10 +111,10 @@ func (a *Adapter) close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Adapter) createTable() {
|
func (a *Adapter) createTable() {
|
||||||
showSql, _ := beego.AppConfig.Bool("showSql")
|
showSql, _ := conf.GetConfigBool("showSql")
|
||||||
a.Engine.ShowSQL(showSql)
|
a.Engine.ShowSQL(showSql)
|
||||||
|
|
||||||
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
|
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||||
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
||||||
a.Engine.SetTableMapper(tbMapper)
|
a.Engine.SetTableMapper(tbMapper)
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
|||||||
application.OrganizationObj.PasswordSalt = "***"
|
application.OrganizationObj.PasswordSalt = "***"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return application
|
return application
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMaskedApplications(applications []*Application, userId string) []*Application {
|
func GetMaskedApplications(applications []*Application, userId string) []*Application {
|
||||||
|
@ -19,14 +19,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultStorageProvider *Provider = nil
|
var defaultStorageProvider *Provider = nil
|
||||||
|
|
||||||
func InitDefaultStorageProvider() {
|
func InitDefaultStorageProvider() {
|
||||||
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider")
|
defaultStorageProviderStr := conf.GetConfigString("defaultStorageProvider")
|
||||||
if defaultStorageProviderStr != "" {
|
if defaultStorageProviderStr != "" {
|
||||||
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
|
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,10 @@ type Cert struct {
|
|||||||
BitSize int `json:"bitSize"`
|
BitSize int `json:"bitSize"`
|
||||||
ExpireInYears int `json:"expireInYears"`
|
ExpireInYears int `json:"expireInYears"`
|
||||||
|
|
||||||
PublicKey string `xorm:"mediumtext" json:"publicKey"`
|
PublicKey string `xorm:"mediumtext" json:"publicKey"`
|
||||||
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||||
|
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
|
||||||
|
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMaskedCert(cert *Cert) *Cert {
|
func GetMaskedCert(cert *Cert) *Cert {
|
||||||
|
@ -179,16 +179,16 @@ func CheckUserPassword(organization string, username string, password string) (*
|
|||||||
if user.IsForbidden {
|
if user.IsForbidden {
|
||||||
return nil, "the user is forbidden to sign in, please contact the administrator"
|
return nil, "the user is forbidden to sign in, please contact the administrator"
|
||||||
}
|
}
|
||||||
//for ldap users
|
|
||||||
if user.Ldap != "" {
|
if user.Ldap != "" {
|
||||||
|
//ONLY for ldap users
|
||||||
return checkLdapUserPassword(user, password)
|
return checkLdapUserPassword(user, password)
|
||||||
|
} else {
|
||||||
|
msg := CheckPassword(user, password)
|
||||||
|
if msg != "" {
|
||||||
|
return nil, msg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := CheckPassword(user, password)
|
|
||||||
if msg != "" {
|
|
||||||
return nil, msg
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, ""
|
return user, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,29 +15,25 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"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() {
|
func InitDb() {
|
||||||
initBuiltInOrganization()
|
existed := initBuiltInOrganization()
|
||||||
initBuiltInUser()
|
if !existed {
|
||||||
initBuiltInApplication()
|
initBuiltInUser()
|
||||||
initBuiltInCert()
|
initBuiltInApplication()
|
||||||
initBuiltInLdap()
|
initBuiltInCert()
|
||||||
|
initBuiltInLdap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBuiltInOrganization() {
|
func initBuiltInOrganization() bool {
|
||||||
organization := getOrganization("admin", "built-in")
|
organization := getOrganization("admin", "built-in")
|
||||||
if organization != nil {
|
if organization != nil {
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
organization = &Organization{
|
organization = &Organization{
|
||||||
@ -47,11 +43,13 @@ func initBuiltInOrganization() {
|
|||||||
DisplayName: "Built-in Organization",
|
DisplayName: "Built-in Organization",
|
||||||
WebsiteUrl: "https://example.com",
|
WebsiteUrl: "https://example.com",
|
||||||
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
||||||
|
PasswordType: "plain",
|
||||||
PhonePrefix: "86",
|
PhonePrefix: "86",
|
||||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||||
PasswordType: "plain",
|
Tags: []string{},
|
||||||
}
|
}
|
||||||
AddOrganization(organization)
|
AddOrganization(organization)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBuiltInUser() {
|
func initBuiltInUser() {
|
||||||
@ -121,7 +119,22 @@ func initBuiltInApplication() {
|
|||||||
AddApplication(application)
|
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() {
|
func initBuiltInCert() {
|
||||||
|
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile()
|
||||||
cert := getCert("admin", "cert-built-in")
|
cert := getCert("admin", "cert-built-in")
|
||||||
if cert != nil {
|
if cert != nil {
|
||||||
return
|
return
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ func getOriginFromHost(host string) (string, string) {
|
|||||||
func GetOidcDiscovery(host string) OidcDiscovery {
|
func GetOidcDiscovery(host string) OidcDiscovery {
|
||||||
originFrontend, originBackend := getOriginFromHost(host)
|
originFrontend, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
origin := beego.AppConfig.String("origin")
|
origin := conf.GetConfigString("origin")
|
||||||
if origin != "" {
|
if origin != "" {
|
||||||
originFrontend = origin
|
originFrontend = origin
|
||||||
originBackend = origin
|
originBackend = origin
|
||||||
|
@ -25,15 +25,16 @@ type Organization struct {
|
|||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||||
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) int {
|
func GetOrganizationCount(owner, field, value string) int {
|
||||||
|
@ -16,6 +16,7 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
@ -27,15 +28,22 @@ type Payment struct {
|
|||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
|
||||||
Provider string `xorm:"varchar(100)" json:"provider"`
|
Provider string `xorm:"varchar(100)" json:"provider"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
User string `xorm:"varchar(100)" json:"user"`
|
User string `xorm:"varchar(100)" json:"user"`
|
||||||
Good string `xorm:"varchar(100)" json:"good"`
|
ProductName string `xorm:"varchar(100)" json:"productName"`
|
||||||
Amount string `xorm:"varchar(100)" json:"amount"`
|
ProductDisplayName string `xorm:"varchar(100)" json:"productDisplayName"`
|
||||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
|
||||||
|
|
||||||
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 {
|
func GetPaymentCount(owner, field, value string) int {
|
||||||
@ -58,6 +66,16 @@ func GetPayments(owner string) []*Payment {
|
|||||||
return payments
|
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 {
|
func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Payment {
|
||||||
payments := []*Payment{}
|
payments := []*Payment{}
|
||||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
@ -124,6 +142,61 @@ func DeletePayment(payment *Payment) bool {
|
|||||||
return affected != 0
|
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 {
|
func (payment *Payment) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,11 @@ type Product struct {
|
|||||||
Detail string `xorm:"varchar(100)" json:"detail"`
|
Detail string `xorm:"varchar(100)" json:"detail"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||||
Price int `json:"price"`
|
Price float64 `json:"price"`
|
||||||
Quantity int `json:"quantity"`
|
Quantity int `json:"quantity"`
|
||||||
Sold int `json:"sold"`
|
Sold int `json:"sold"`
|
||||||
Providers []string `xorm:"varchar(100)" json:"providers"`
|
Providers []string `xorm:"varchar(100)" json:"providers"`
|
||||||
|
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||||
|
|
||||||
State string `xorm:"varchar(100)" json:"state"`
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
}
|
}
|
||||||
@ -128,3 +129,82 @@ func DeleteProduct(product *Product) bool {
|
|||||||
func (product *Product) GetId() string {
|
func (product *Product) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", product.Owner, product.Name)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/pp"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
@ -32,9 +33,10 @@ type Provider struct {
|
|||||||
SubType string `xorm:"varchar(100)" json:"subType"`
|
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||||
Method string `xorm:"varchar(100)" json:"method"`
|
Method string `xorm:"varchar(100)" json:"method"`
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
||||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||||
|
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||||
|
|
||||||
Host string `xorm:"varchar(100)" json:"host"`
|
Host string `xorm:"varchar(100)" json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
@ -181,6 +183,23 @@ func DeleteProvider(provider *Provider) bool {
|
|||||||
return affected != 0
|
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 {
|
func (p *Provider) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,9 @@ func (application *Application) GetProviderItem(providerName string) *ProviderIt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pi *ProviderItem) IsProviderVisible() bool {
|
func (pi *ProviderItem) IsProviderVisible() bool {
|
||||||
|
if pi.Provider == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
|
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ var logPostOnly bool
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
logPostOnly, err = beego.AppConfig.Bool("logPostOnly")
|
logPostOnly, err = conf.GetConfigBool("logPostOnly")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//panic(err)
|
//panic(err)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(200) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
User string `xorm:"varchar(100)" json:"user"`
|
User string `xorm:"varchar(100)" json:"user"`
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
saml2 "github.com/russellhaering/gosaml2"
|
saml2 "github.com/russellhaering/gosaml2"
|
||||||
dsig "github.com/russellhaering/goxmldsig"
|
dsig "github.com/russellhaering/goxmldsig"
|
||||||
)
|
)
|
||||||
@ -73,7 +73,7 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
|
|||||||
certStore := dsig.MemoryX509CertificateStore{
|
certStore := dsig.MemoryX509CertificateStore{
|
||||||
Roots: []*x509.Certificate{},
|
Roots: []*x509.Certificate{},
|
||||||
}
|
}
|
||||||
origin := beego.AppConfig.String("origin")
|
origin := conf.GetConfigString("origin")
|
||||||
certEncodedData := ""
|
certEncodedData := ""
|
||||||
if samlResponse != "" {
|
if samlResponse != "" {
|
||||||
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
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 {
|
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)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/storage"
|
"github.com/casdoor/casdoor/storage"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@ -28,7 +28,7 @@ var isCloudIntranet bool
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
isCloudIntranet, err = beego.AppConfig.Bool("isCloudIntranet")
|
isCloudIntranet, err = conf.GetConfigBool("isCloudIntranet")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//panic(err)
|
//panic(err)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//go:build !skipCi
|
//go:build !skipCi
|
||||||
// +build !skipCi
|
// +build !skipCi
|
||||||
|
|
||||||
|
@ -379,7 +379,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
Scope: "",
|
Scope: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if application.ClientSecret != clientSecret {
|
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: invalid client_secret",
|
AccessToken: "error: invalid client_secret",
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
@ -439,14 +439,15 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
}
|
}
|
||||||
AddToken(newToken)
|
AddToken(newToken)
|
||||||
|
DeleteToken(&token)
|
||||||
|
|
||||||
tokenWrapper := &TokenWrapper{
|
tokenWrapper := &TokenWrapper{
|
||||||
AccessToken: token.AccessToken,
|
AccessToken: newToken.AccessToken,
|
||||||
IdToken: token.AccessToken,
|
IdToken: newToken.AccessToken,
|
||||||
RefreshToken: token.RefreshToken,
|
RefreshToken: newToken.RefreshToken,
|
||||||
TokenType: token.TokenType,
|
TokenType: newToken.TokenType,
|
||||||
ExpiresIn: token.ExpiresIn,
|
ExpiresIn: newToken.ExpiresIn,
|
||||||
Scope: token.Scope,
|
Scope: newToken.Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokenWrapper
|
return tokenWrapper
|
||||||
@ -521,7 +522,8 @@ func GetPasswordToken(application *Application, username string, password string
|
|||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, errors.New("error: the user does not exist")
|
return nil, errors.New("error: the user does not exist")
|
||||||
}
|
}
|
||||||
if user.Password != password {
|
msg := CheckPassword(user, password)
|
||||||
|
if msg != "" {
|
||||||
return nil, errors.New("error: invalid username or password")
|
return nil, errors.New("error: invalid username or password")
|
||||||
}
|
}
|
||||||
if user.IsForbidden {
|
if user.IsForbidden {
|
||||||
@ -556,7 +558,9 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
|||||||
return nil, errors.New("error: invalid client_secret")
|
return nil, errors.New("error: invalid client_secret")
|
||||||
}
|
}
|
||||||
nullUser := &User{
|
nullUser := &User{
|
||||||
Name: fmt.Sprintf("app/%s", application.Name),
|
Owner: application.Owner,
|
||||||
|
Id: application.GetId(),
|
||||||
|
Name: fmt.Sprintf("app/%s", application.Name),
|
||||||
}
|
}
|
||||||
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
|
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
236
object/token_cas.go
Normal file
236
object/token_cas.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CasServiceResponse struct {
|
||||||
|
XMLName xml.Name `xml:"cas:serviceResponse" json:"-"`
|
||||||
|
Xmlns string `xml:"xmlns:cas,attr"`
|
||||||
|
Failure *CasAuthenticationFailure
|
||||||
|
Success *CasAuthenticationSuccess
|
||||||
|
ProxySuccess *CasProxySuccess
|
||||||
|
ProxyFailure *CasProxyFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasAuthenticationFailure struct {
|
||||||
|
XMLName xml.Name `xml:"cas:authenticationFailure" json:"-"`
|
||||||
|
Code string `xml:"code,attr"`
|
||||||
|
Message string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasAuthenticationSuccess struct {
|
||||||
|
XMLName xml.Name `xml:"cas:authenticationSuccess" json:"-"`
|
||||||
|
User string `xml:"cas:user"`
|
||||||
|
ProxyGrantingTicket string `xml:"cas:proxyGrantingTicket,omitempty"`
|
||||||
|
Proxies *CasProxies `xml:"cas:proxies"`
|
||||||
|
Attributes *CasAttributes `xml:"cas:attributes"`
|
||||||
|
ExtraAttributes []*CasAnyAttribute `xml:",any"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasProxies struct {
|
||||||
|
XMLName xml.Name `xml:"cas:proxies" json:"-"`
|
||||||
|
Proxies []string `xml:"cas:proxy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasAttributes struct {
|
||||||
|
XMLName xml.Name `xml:"cas:attributes" json:"-"`
|
||||||
|
AuthenticationDate time.Time `xml:"cas:authenticationDate"`
|
||||||
|
LongTermAuthenticationRequestTokenUsed bool `xml:"cas:longTermAuthenticationRequestTokenUsed"`
|
||||||
|
IsFromNewLogin bool `xml:"cas:isFromNewLogin"`
|
||||||
|
MemberOf []string `xml:"cas:memberOf"`
|
||||||
|
UserAttributes *CasUserAttributes
|
||||||
|
ExtraAttributes []*CasAnyAttribute `xml:",any"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasUserAttributes struct {
|
||||||
|
XMLName xml.Name `xml:"cas:userAttributes" json:"-"`
|
||||||
|
Attributes []*CasNamedAttribute `xml:"cas:attribute"`
|
||||||
|
AnyAttributes []*CasAnyAttribute `xml:",any"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasNamedAttribute struct {
|
||||||
|
XMLName xml.Name `xml:"cas:attribute" json:"-"`
|
||||||
|
Name string `xml:"name,attr,omitempty"`
|
||||||
|
Value string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasAnyAttribute struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasAuthenticationSuccessWrapper struct {
|
||||||
|
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
|
||||||
|
Service string //to which service this token is issued
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasProxySuccess struct {
|
||||||
|
XMLName xml.Name `xml:"cas:proxySuccess" json:"-"`
|
||||||
|
ProxyTicket string `xml:"cas:proxyTicket"`
|
||||||
|
}
|
||||||
|
type CasProxyFailure struct {
|
||||||
|
XMLName xml.Name `xml:"cas:proxyFailure" json:"-"`
|
||||||
|
Code string `xml:"code,attr"`
|
||||||
|
Message string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//st is short for service ticket
|
||||||
|
var stToServiceResponse sync.Map
|
||||||
|
|
||||||
|
//pgt is short for proxy granting ticket
|
||||||
|
var pgtToServiceResponse sync.Map
|
||||||
|
|
||||||
|
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service string) string {
|
||||||
|
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
|
||||||
|
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{
|
||||||
|
AuthenticationSuccess: token,
|
||||||
|
Service: service,
|
||||||
|
})
|
||||||
|
return pgt
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateId() {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string) {
|
||||||
|
if responseWrapperType, ok := pgtToServiceResponse.LoadAndDelete(pgt); ok {
|
||||||
|
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
|
||||||
|
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service
|
||||||
|
}
|
||||||
|
return false, nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCasTokenByTicket(ticket string) (bool, *CasAuthenticationSuccess, string) {
|
||||||
|
if responseWrapperType, ok := stToServiceResponse.LoadAndDelete(ticket); ok {
|
||||||
|
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
|
||||||
|
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service
|
||||||
|
}
|
||||||
|
return false, nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService string) string {
|
||||||
|
proxyTicket := fmt.Sprintf("PT-%s", util.GenerateId())
|
||||||
|
stToServiceResponse.Store(proxyTicket, &CasAuthenticationSuccessWrapper{
|
||||||
|
AuthenticationSuccess: token,
|
||||||
|
Service: targetService,
|
||||||
|
})
|
||||||
|
return proxyTicket
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateCasToken(userId string, service string) (string, error) {
|
||||||
|
|
||||||
|
if user := GetUser(userId); user != nil {
|
||||||
|
authenticationSuccess := CasAuthenticationSuccess{
|
||||||
|
User: user.Name,
|
||||||
|
Attributes: &CasAttributes{
|
||||||
|
AuthenticationDate: time.Now(),
|
||||||
|
UserAttributes: &CasUserAttributes{},
|
||||||
|
},
|
||||||
|
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
tmp := map[string]string{}
|
||||||
|
json.Unmarshal(data, &tmp)
|
||||||
|
for k, v := range tmp {
|
||||||
|
if v != "" {
|
||||||
|
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
||||||
|
Name: k,
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st := fmt.Sprintf("ST-%d", rand.Int())
|
||||||
|
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
|
||||||
|
AuthenticationSuccess: &authenticationSuccess,
|
||||||
|
Service: service,
|
||||||
|
})
|
||||||
|
return st, nil
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("invalid user Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CasAuthenticationSuccess) DeepCopy() CasAuthenticationSuccess {
|
||||||
|
res := *c
|
||||||
|
//copy proxy
|
||||||
|
if c.Proxies != nil {
|
||||||
|
tmp := c.Proxies.DeepCopy()
|
||||||
|
res.Proxies = &tmp
|
||||||
|
}
|
||||||
|
if c.Attributes != nil {
|
||||||
|
tmp := c.Attributes.DeepCopy()
|
||||||
|
res.Attributes = &tmp
|
||||||
|
}
|
||||||
|
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
|
||||||
|
for i, e := range c.ExtraAttributes {
|
||||||
|
tmp := *e
|
||||||
|
res.ExtraAttributes[i] = &tmp
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CasProxies) DeepCopy() CasProxies {
|
||||||
|
res := CasProxies{
|
||||||
|
Proxies: make([]string, len(c.Proxies)),
|
||||||
|
}
|
||||||
|
copy(res.Proxies, c.Proxies)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CasAttributes) DeepCopy() CasAttributes {
|
||||||
|
res := *c
|
||||||
|
if c.MemberOf != nil {
|
||||||
|
res.MemberOf = make([]string, len(c.MemberOf))
|
||||||
|
copy(res.MemberOf, c.MemberOf)
|
||||||
|
}
|
||||||
|
tmp := c.UserAttributes.DeepCopy()
|
||||||
|
res.UserAttributes = &tmp
|
||||||
|
|
||||||
|
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
|
||||||
|
for i, e := range c.ExtraAttributes {
|
||||||
|
tmp := *e
|
||||||
|
res.ExtraAttributes[i] = &tmp
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CasUserAttributes) DeepCopy() CasUserAttributes {
|
||||||
|
res := CasUserAttributes{
|
||||||
|
AnyAttributes: make([]*CasAnyAttribute, len(c.AnyAttributes)),
|
||||||
|
Attributes: make([]*CasNamedAttribute, len(c.Attributes)),
|
||||||
|
}
|
||||||
|
for i, a := range c.AnyAttributes {
|
||||||
|
var tmp = *a
|
||||||
|
res.AnyAttributes[i] = &tmp
|
||||||
|
}
|
||||||
|
for i, a := range c.Attributes {
|
||||||
|
var tmp = *a
|
||||||
|
res.Attributes[i] = &tmp
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
@ -15,11 +15,10 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"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)
|
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||||
|
|
||||||
user.Password = ""
|
user.Password = ""
|
||||||
origin := beego.AppConfig.String("origin")
|
origin := conf.GetConfigString("origin")
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
if origin != "" {
|
if origin != "" {
|
||||||
originBackend = origin
|
originBackend = origin
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
@ -85,6 +85,8 @@ type User struct {
|
|||||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||||
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
||||||
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||||
|
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
|
||||||
|
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
||||||
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
||||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||||
@ -303,7 +305,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
|||||||
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"}
|
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"}
|
||||||
}
|
}
|
||||||
if isGlobalAdmin {
|
if isGlobalAdmin {
|
||||||
columns = append(columns, "name")
|
columns = append(columns, "name", "email", "phone")
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
|
||||||
@ -380,7 +382,9 @@ func AddUsers(users []*User) bool {
|
|||||||
|
|
||||||
affected, err := adapter.Engine.Insert(users)
|
affected, err := adapter.Engine.Insert(users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
if !strings.Contains(err.Error(), "Duplicate entry") {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected != 0
|
return affected != 0
|
||||||
@ -426,7 +430,7 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
|
|||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
|
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
|
||||||
}
|
}
|
||||||
origin := beego.AppConfig.String("origin")
|
origin := conf.GetConfigString("origin")
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
if origin != "" {
|
if origin != "" {
|
||||||
originBackend = origin
|
originBackend = origin
|
||||||
|
@ -52,8 +52,8 @@ func UploadUsers(owner string, fileId string) bool {
|
|||||||
|
|
||||||
oldUserMap := getUserMap(owner)
|
oldUserMap := getUserMap(owner)
|
||||||
newUsers := []*User{}
|
newUsers := []*User{}
|
||||||
for _, line := range table {
|
for index, line := range table {
|
||||||
if parseLineItem(&line, 0) == "" {
|
if index == 0 || parseLineItem(&line, 0) == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,38 +67,42 @@ func UploadUsers(owner string, fileId string) bool {
|
|||||||
Password: parseLineItem(&line, 6),
|
Password: parseLineItem(&line, 6),
|
||||||
PasswordSalt: parseLineItem(&line, 7),
|
PasswordSalt: parseLineItem(&line, 7),
|
||||||
DisplayName: parseLineItem(&line, 8),
|
DisplayName: parseLineItem(&line, 8),
|
||||||
Avatar: parseLineItem(&line, 9),
|
FirstName: parseLineItem(&line, 9),
|
||||||
|
LastName: parseLineItem(&line, 10),
|
||||||
|
Avatar: parseLineItem(&line, 11),
|
||||||
PermanentAvatar: "",
|
PermanentAvatar: "",
|
||||||
Email: parseLineItem(&line, 10),
|
Email: parseLineItem(&line, 12),
|
||||||
Phone: parseLineItem(&line, 11),
|
Phone: parseLineItem(&line, 13),
|
||||||
Location: parseLineItem(&line, 12),
|
Location: parseLineItem(&line, 14),
|
||||||
Address: []string{parseLineItem(&line, 13)},
|
Address: []string{parseLineItem(&line, 15)},
|
||||||
Affiliation: parseLineItem(&line, 14),
|
Affiliation: parseLineItem(&line, 16),
|
||||||
Title: parseLineItem(&line, 15),
|
Title: parseLineItem(&line, 17),
|
||||||
IdCardType: parseLineItem(&line, 16),
|
IdCardType: parseLineItem(&line, 18),
|
||||||
IdCard: parseLineItem(&line, 17),
|
IdCard: parseLineItem(&line, 19),
|
||||||
Homepage: parseLineItem(&line, 18),
|
Homepage: parseLineItem(&line, 20),
|
||||||
Bio: parseLineItem(&line, 19),
|
Bio: parseLineItem(&line, 21),
|
||||||
Tag: parseLineItem(&line, 20),
|
Tag: parseLineItem(&line, 22),
|
||||||
Region: parseLineItem(&line, 21),
|
Region: parseLineItem(&line, 23),
|
||||||
Language: parseLineItem(&line, 22),
|
Language: parseLineItem(&line, 24),
|
||||||
Gender: parseLineItem(&line, 23),
|
Gender: parseLineItem(&line, 25),
|
||||||
Birthday: parseLineItem(&line, 24),
|
Birthday: parseLineItem(&line, 26),
|
||||||
Education: parseLineItem(&line, 25),
|
Education: parseLineItem(&line, 27),
|
||||||
Score: parseLineItemInt(&line, 26),
|
Score: parseLineItemInt(&line, 28),
|
||||||
Ranking: parseLineItemInt(&line, 27),
|
Karma: parseLineItemInt(&line, 29),
|
||||||
|
Ranking: parseLineItemInt(&line, 30),
|
||||||
IsDefaultAvatar: false,
|
IsDefaultAvatar: false,
|
||||||
IsOnline: parseLineItemBool(&line, 28),
|
IsOnline: parseLineItemBool(&line, 31),
|
||||||
IsAdmin: parseLineItemBool(&line, 29),
|
IsAdmin: parseLineItemBool(&line, 32),
|
||||||
IsGlobalAdmin: parseLineItemBool(&line, 30),
|
IsGlobalAdmin: parseLineItemBool(&line, 33),
|
||||||
IsForbidden: parseLineItemBool(&line, 31),
|
IsForbidden: parseLineItemBool(&line, 34),
|
||||||
IsDeleted: parseLineItemBool(&line, 32),
|
IsDeleted: parseLineItemBool(&line, 35),
|
||||||
SignupApplication: parseLineItem(&line, 33),
|
SignupApplication: parseLineItem(&line, 36),
|
||||||
Hash: "",
|
Hash: "",
|
||||||
PreHash: "",
|
PreHash: "",
|
||||||
CreatedIp: parseLineItem(&line, 34),
|
CreatedIp: parseLineItem(&line, 37),
|
||||||
LastSigninTime: parseLineItem(&line, 35),
|
LastSigninTime: parseLineItem(&line, 38),
|
||||||
LastSigninIp: parseLineItem(&line, 36),
|
LastSigninIp: parseLineItem(&line, 39),
|
||||||
|
Ldap: "",
|
||||||
Properties: map[string]string{},
|
Properties: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
@ -129,7 +129,7 @@ func CheckVerificationCode(dest, code string) string {
|
|||||||
return "Code has not been sent yet!"
|
return "Code has not been sent yet!"
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout, err := beego.AppConfig.Int64("verificationCodeTimeout")
|
timeout, err := conf.GetConfigInt64("verificationCodeTimeout")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ func isAddressOpen(address string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getProxyHttpClient() *http.Client {
|
func getProxyHttpClient() *http.Client {
|
||||||
sock5Proxy := beego.AppConfig.String("sock5Proxy")
|
sock5Proxy := conf.GetConfigString("sock5Proxy")
|
||||||
if sock5Proxy == "" {
|
if sock5Proxy == "" {
|
||||||
return &http.Client{}
|
return &http.Client{}
|
||||||
}
|
}
|
||||||
|
@ -84,8 +84,8 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
|
|
||||||
if path == "/api/delete-resource" {
|
if path == "/api/delete-resource" {
|
||||||
tokens := strings.Split(obj.Name, "/")
|
tokens := strings.Split(obj.Name, "/")
|
||||||
if len(tokens) >= 2 {
|
if len(tokens) >= 5 {
|
||||||
obj.Name = tokens[len(tokens)-2]
|
obj.Name = tokens[4]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,10 +100,17 @@ func willLog(subOwner string, subName string, method string, urlPath string, obj
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUrlPath(urlPath string) string {
|
||||||
|
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate")) {
|
||||||
|
return "/cas"
|
||||||
|
}
|
||||||
|
return urlPath
|
||||||
|
}
|
||||||
|
|
||||||
func AuthzFilter(ctx *context.Context) {
|
func AuthzFilter(ctx *context.Context) {
|
||||||
subOwner, subName := getSubject(ctx)
|
subOwner, subName := getSubject(ctx)
|
||||||
method := ctx.Request.Method
|
method := ctx.Request.Method
|
||||||
urlPath := ctx.Request.URL.Path
|
urlPath := getUrlPath(ctx.Request.URL.Path)
|
||||||
objOwner, objName := getObject(ctx)
|
objOwner, objName := getObject(ctx)
|
||||||
|
|
||||||
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)
|
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)
|
||||||
|
@ -29,10 +29,8 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
|
|
||||||
// GET parameter like "/page?access_token=123" or
|
// GET parameter like "/page?access_token=123" or
|
||||||
// HTTP Bearer token like "Authorization: Bearer 123"
|
// HTTP Bearer token like "Authorization: Bearer 123"
|
||||||
accessToken := ctx.Input.Query("accessToken")
|
accessToken := util.GetMaxLenStr(ctx.Input.Query("accessToken"), ctx.Input.Query("access_token"), parseBearerToken(ctx))
|
||||||
if accessToken == "" {
|
|
||||||
accessToken = parseBearerToken(ctx)
|
|
||||||
}
|
|
||||||
if accessToken != "" {
|
if accessToken != "" {
|
||||||
token := object.GetTokenByAccessToken(accessToken)
|
token := object.GetTokenByAccessToken(accessToken)
|
||||||
if token == nil {
|
if token == nil {
|
||||||
|
@ -156,16 +156,25 @@ func initAPI() {
|
|||||||
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
|
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
|
||||||
beego.Router("/api/add-product", &controllers.ApiController{}, "POST:AddProduct")
|
beego.Router("/api/add-product", &controllers.ApiController{}, "POST:AddProduct")
|
||||||
beego.Router("/api/delete-product", &controllers.ApiController{}, "POST:DeleteProduct")
|
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-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/get-payment", &controllers.ApiController{}, "GET:GetPayment")
|
||||||
beego.Router("/api/update-payment", &controllers.ApiController{}, "POST:UpdatePayment")
|
beego.Router("/api/update-payment", &controllers.ApiController{}, "POST:UpdatePayment")
|
||||||
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
|
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
|
||||||
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
|
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-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||||
|
|
||||||
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
||||||
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
||||||
|
|
||||||
|
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceAndProxyValidate")
|
||||||
|
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasServiceAndProxyValidate")
|
||||||
|
beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy")
|
||||||
|
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,9 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
if strings.HasPrefix(urlPath, "/api/") || strings.HasPrefix(urlPath, "/.well-known/") {
|
if strings.HasPrefix(urlPath, "/api/") || strings.HasPrefix(urlPath, "/.well-known/") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
path := "web/build"
|
path := "web/build"
|
||||||
if urlPath == "/" {
|
if urlPath == "/" {
|
||||||
|
@ -478,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": {
|
"/api/check-ldap-users-exist": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -1710,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": {
|
"/api/get-users": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -1936,6 +2012,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/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": {
|
"/api/login/oauth/logout": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -2015,7 +2121,6 @@
|
|||||||
"in": "query",
|
"in": "query",
|
||||||
"name": "client_secret",
|
"name": "client_secret",
|
||||||
"description": "OAuth client secret",
|
"description": "OAuth client secret",
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -2046,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": {
|
"/api/send-verification-code": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -2664,11 +2797,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"2015.0xc0000edb90.false": {
|
"2026.0xc000380de0.false": {
|
||||||
"title": "false",
|
"title": "false",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"2049.0xc0000edbc0.false": {
|
"2060.0xc000380e10.false": {
|
||||||
"title": "false",
|
"title": "false",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
@ -2685,10 +2818,10 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/2015.0xc0000edb90.false"
|
"$ref": "#/definitions/2026.0xc000380de0.false"
|
||||||
},
|
},
|
||||||
"data2": {
|
"data2": {
|
||||||
"$ref": "#/definitions/2049.0xc0000edbc0.false"
|
"$ref": "#/definitions/2060.0xc000380e10.false"
|
||||||
},
|
},
|
||||||
"msg": {
|
"msg": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -2709,10 +2842,10 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/2015.0xc0000edb90.false"
|
"$ref": "#/definitions/2026.0xc000380de0.false"
|
||||||
},
|
},
|
||||||
"data2": {
|
"data2": {
|
||||||
"$ref": "#/definitions/2049.0xc0000edbc0.false"
|
"$ref": "#/definitions/2060.0xc000380e10.false"
|
||||||
},
|
},
|
||||||
"msg": {
|
"msg": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -2864,6 +2997,12 @@
|
|||||||
"title": "Cert",
|
"title": "Cert",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"authorityPublicKey": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"authorityRootPublicKey": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"bitSize": {
|
"bitSize": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
@ -2913,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": {
|
"object.Organization": {
|
||||||
"title": "Organization",
|
"title": "Organization",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -2950,6 +3137,12 @@
|
|||||||
"phonePrefix": {
|
"phonePrefix": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"websiteUrl": {
|
"websiteUrl": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -2959,19 +3152,19 @@
|
|||||||
"title": "Payment",
|
"title": "Payment",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"amount": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"createdTime": {
|
"createdTime": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"currency": {
|
"currency": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"detail": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"displayName": {
|
"displayName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"good": {
|
"message": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
@ -2983,12 +3176,31 @@
|
|||||||
"owner": {
|
"owner": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"payUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"productDisplayName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"productName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"provider": {
|
"provider": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"returnUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -3074,8 +3286,8 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"price": {
|
"price": {
|
||||||
"type": "integer",
|
"type": "number",
|
||||||
"format": "int64"
|
"format": "double"
|
||||||
},
|
},
|
||||||
"providers": {
|
"providers": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@ -3087,6 +3299,9 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
|
"returnUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"sold": {
|
"sold": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
@ -3112,6 +3327,9 @@
|
|||||||
"category": {
|
"category": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"cert": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"clientId": {
|
"clientId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -3482,6 +3700,9 @@
|
|||||||
"birthday": {
|
"birthday": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"casdoor": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"createdIp": {
|
"createdIp": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -309,6 +309,28 @@ paths:
|
|||||||
description: object
|
description: object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$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:
|
/api/check-ldap-users-exist:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1111,6 +1133,35 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: '{int} int The count of filtered users for an organization'
|
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:
|
/api/get-users:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1262,6 +1313,26 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.TokenWrapper'
|
$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:
|
/api/login/oauth/logout:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -1318,7 +1389,6 @@ paths:
|
|||||||
- in: query
|
- in: query
|
||||||
name: client_secret
|
name: client_secret
|
||||||
description: OAuth client secret
|
description: OAuth client secret
|
||||||
required: true
|
|
||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
@ -1336,6 +1406,24 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$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:
|
/api/send-verification-code:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -1743,10 +1831,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.Userinfo'
|
$ref: '#/definitions/object.Userinfo'
|
||||||
definitions:
|
definitions:
|
||||||
2015.0xc0000edb90.false:
|
2026.0xc000380de0.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
2049.0xc0000edbc0.false:
|
2060.0xc000380e10.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
RequestForm:
|
RequestForm:
|
||||||
@ -1760,9 +1848,9 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/2015.0xc0000edb90.false'
|
$ref: '#/definitions/2026.0xc000380de0.false'
|
||||||
data2:
|
data2:
|
||||||
$ref: '#/definitions/2049.0xc0000edbc0.false'
|
$ref: '#/definitions/2060.0xc000380e10.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -1776,9 +1864,9 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/2015.0xc0000edb90.false'
|
$ref: '#/definitions/2026.0xc000380de0.false'
|
||||||
data2:
|
data2:
|
||||||
$ref: '#/definitions/2049.0xc0000edbc0.false'
|
$ref: '#/definitions/2060.0xc000380e10.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
@ -1880,6 +1968,10 @@ definitions:
|
|||||||
title: Cert
|
title: Cert
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
authorityPublicKey:
|
||||||
|
type: string
|
||||||
|
authorityRootPublicKey:
|
||||||
|
type: string
|
||||||
bitSize:
|
bitSize:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
@ -1912,6 +2004,39 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
value:
|
value:
|
||||||
type: string
|
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:
|
object.Organization:
|
||||||
title: Organization
|
title: Organization
|
||||||
type: object
|
type: object
|
||||||
@ -1938,21 +2063,25 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
phonePrefix:
|
phonePrefix:
|
||||||
type: string
|
type: string
|
||||||
|
tags:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
websiteUrl:
|
websiteUrl:
|
||||||
type: string
|
type: string
|
||||||
object.Payment:
|
object.Payment:
|
||||||
title: Payment
|
title: Payment
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
amount:
|
|
||||||
type: string
|
|
||||||
createdTime:
|
createdTime:
|
||||||
type: string
|
type: string
|
||||||
currency:
|
currency:
|
||||||
type: string
|
type: string
|
||||||
|
detail:
|
||||||
|
type: string
|
||||||
displayName:
|
displayName:
|
||||||
type: string
|
type: string
|
||||||
good:
|
message:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
@ -1960,10 +2089,23 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
|
payUrl:
|
||||||
|
type: string
|
||||||
|
price:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
productDisplayName:
|
||||||
|
type: string
|
||||||
|
productName:
|
||||||
|
type: string
|
||||||
provider:
|
provider:
|
||||||
type: string
|
type: string
|
||||||
|
returnUrl:
|
||||||
|
type: string
|
||||||
state:
|
state:
|
||||||
type: string
|
type: string
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
user:
|
user:
|
||||||
@ -2021,8 +2163,8 @@ definitions:
|
|||||||
owner:
|
owner:
|
||||||
type: string
|
type: string
|
||||||
price:
|
price:
|
||||||
type: integer
|
type: number
|
||||||
format: int64
|
format: double
|
||||||
providers:
|
providers:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
@ -2030,6 +2172,8 @@ definitions:
|
|||||||
quantity:
|
quantity:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
returnUrl:
|
||||||
|
type: string
|
||||||
sold:
|
sold:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
@ -2047,6 +2191,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
category:
|
category:
|
||||||
type: string
|
type: string
|
||||||
|
cert:
|
||||||
|
type: string
|
||||||
clientId:
|
clientId:
|
||||||
type: string
|
type: string
|
||||||
clientId2:
|
clientId2:
|
||||||
@ -2296,6 +2442,8 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
birthday:
|
birthday:
|
||||||
type: string
|
type: string
|
||||||
|
casdoor:
|
||||||
|
type: string
|
||||||
createdIp:
|
createdIp:
|
||||||
type: string
|
type: string
|
||||||
createdTime:
|
createdTime:
|
||||||
|
@ -20,10 +20,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var rePhoneCn *regexp.Regexp
|
var rePhoneCn *regexp.Regexp
|
||||||
|
var rePhone *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// https://learnku.com/articles/31543
|
// 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}$`)
|
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 {
|
func IsEmailValid(email string) bool {
|
||||||
@ -34,3 +36,7 @@ func IsEmailValid(email string) bool {
|
|||||||
func IsPhoneCnValid(phone string) bool {
|
func IsPhoneCnValid(phone string) bool {
|
||||||
return rePhoneCn.MatchString(phone)
|
return rePhoneCn.MatchString(phone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMaskedPhone(phone string) string {
|
||||||
|
return rePhone.ReplaceAllString(phone, "$1****$2")
|
||||||
|
}
|
||||||
|
@ -20,9 +20,10 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io/ioutil"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -41,6 +42,15 @@ func ParseInt(s string) int {
|
|||||||
return i
|
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 {
|
func ParseBool(s string) bool {
|
||||||
i := ParseInt(s)
|
i := ParseInt(s)
|
||||||
return i != 0
|
return i != 0
|
||||||
@ -88,6 +98,25 @@ func GenerateId() string {
|
|||||||
return uuid.NewString()
|
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 {
|
func GetId(name string) string {
|
||||||
return fmt.Sprintf("admin/%s", name)
|
return fmt.Sprintf("admin/%s", name)
|
||||||
}
|
}
|
||||||
@ -133,7 +162,7 @@ func GetMinLenStr(strs ...string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReadStringFromPath(path string) string {
|
func ReadStringFromPath(path string) string {
|
||||||
data, err := os.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -142,7 +171,7 @@ func ReadStringFromPath(path string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func WriteStringToPath(s string, path string) {
|
func WriteStringToPath(s string, path string) {
|
||||||
err := os.WriteFile(path, []byte(s), 0644)
|
err := ioutil.WriteFile(path, []byte(s), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -177,3 +206,28 @@ func IsChinese(str string) bool {
|
|||||||
}
|
}
|
||||||
return flag
|
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) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,22 @@ module.exports = {
|
|||||||
'/.well-known/openid-configuration': {
|
'/.well-known/openid-configuration': {
|
||||||
target: 'http://localhost:8000',
|
target: 'http://localhost:8000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/cas/serviceValidate': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/cas/proxyValidate': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/cas/proxy': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/cas/validate': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -29,7 +29,9 @@
|
|||||||
"react-i18next": "^11.8.7",
|
"react-i18next": "^11.8.7",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.3",
|
"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": {
|
"scripts": {
|
||||||
"start": "cross-env PORT=7001 craco start",
|
"start": "cross-env PORT=7001 craco start",
|
||||||
@ -46,12 +48,14 @@
|
|||||||
"production": [
|
"production": [
|
||||||
">0.2%",
|
">0.2%",
|
||||||
"not dead",
|
"not dead",
|
||||||
"not op_mini all"
|
"not op_mini all",
|
||||||
|
"ie > 8"
|
||||||
],
|
],
|
||||||
"development": [
|
"development": [
|
||||||
"last 1 chrome version",
|
"last 1 chrome version",
|
||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version",
|
||||||
|
"ie > 8"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -48,9 +48,11 @@ import ProductEditPage from "./ProductEditPage";
|
|||||||
import ProductBuyPage from "./ProductBuyPage";
|
import ProductBuyPage from "./ProductBuyPage";
|
||||||
import PaymentListPage from "./PaymentListPage";
|
import PaymentListPage from "./PaymentListPage";
|
||||||
import PaymentEditPage from "./PaymentEditPage";
|
import PaymentEditPage from "./PaymentEditPage";
|
||||||
|
import PaymentResultPage from "./PaymentResultPage";
|
||||||
import AccountPage from "./account/AccountPage";
|
import AccountPage from "./account/AccountPage";
|
||||||
import HomePage from "./basic/HomePage";
|
import HomePage from "./basic/HomePage";
|
||||||
import CustomGithubCorner from "./CustomGithubCorner";
|
import CustomGithubCorner from "./CustomGithubCorner";
|
||||||
|
import * as Conf from "./Conf";
|
||||||
|
|
||||||
import * as Auth from "./auth/Auth";
|
import * as Auth from "./auth/Auth";
|
||||||
import SignupPage from "./auth/SignupPage";
|
import SignupPage from "./auth/SignupPage";
|
||||||
@ -66,6 +68,7 @@ import i18next from 'i18next';
|
|||||||
import PromptPage from "./auth/PromptPage";
|
import PromptPage from "./auth/PromptPage";
|
||||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||||
import SamlCallback from './auth/SamlCallback';
|
import SamlCallback from './auth/SamlCallback';
|
||||||
|
import CasLogout from "./auth/CasLogout";
|
||||||
|
|
||||||
const { Header, Footer } = Layout;
|
const { Header, Footer } = Layout;
|
||||||
|
|
||||||
@ -233,8 +236,12 @@ class App extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Setting.showMessage("success", `Logged out successfully`);
|
Setting.showMessage("success", `Logged out successfully`);
|
||||||
|
let redirectUri = res.data2;
|
||||||
Setting.goToLinkSoft(this, "/");
|
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||||
|
Setting.goToLink(redirectUri);
|
||||||
|
}else{
|
||||||
|
Setting.goToLinkSoft(this, "/");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
||||||
}
|
}
|
||||||
@ -434,20 +441,24 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
res.push(
|
|
||||||
<Menu.Item key="/products">
|
if (Conf.EnableExtraPages) {
|
||||||
<Link to="/products">
|
res.push(
|
||||||
{i18next.t("general:Products")}
|
<Menu.Item key="/products">
|
||||||
</Link>
|
<Link to="/products">
|
||||||
</Menu.Item>
|
{i18next.t("general:Products")}
|
||||||
);
|
</Link>
|
||||||
res.push(
|
</Menu.Item>
|
||||||
<Menu.Item key="/payments">
|
);
|
||||||
<Link to="/payments">
|
res.push(
|
||||||
{i18next.t("general:Payments")}
|
<Menu.Item key="/payments">
|
||||||
</Link>
|
<Link to="/payments">
|
||||||
</Menu.Item>
|
{i18next.t("general:Payments")}
|
||||||
);
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/swagger">
|
<Menu.Item key="/swagger">
|
||||||
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
|
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
|
||||||
@ -524,6 +535,7 @@ class App extends Component {
|
|||||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage 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" 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="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
<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.")}
|
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||||
@ -546,22 +558,22 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Menu
|
<div>
|
||||||
// theme="dark"
|
<Menu
|
||||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
// theme="dark"
|
||||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||||
style={{ lineHeight: '64px'}}
|
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||||
>
|
style={{lineHeight: '64px', width: '80%', position: 'absolute'}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
this.renderMenu()
|
||||||
|
}
|
||||||
|
</Menu>
|
||||||
{
|
{
|
||||||
this.renderMenu()
|
this.renderAccount()
|
||||||
}
|
}
|
||||||
<div style = {{float: 'right'}}>
|
<SelectLanguageBox/>
|
||||||
{
|
</div>
|
||||||
this.renderAccount()
|
|
||||||
}
|
|
||||||
<SelectLanguageBox/>
|
|
||||||
</div>
|
|
||||||
</Menu>
|
|
||||||
</Header>
|
</Header>
|
||||||
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
|
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
|
||||||
<Card className="content-warp-card">
|
<Card className="content-warp-card">
|
||||||
@ -631,7 +643,8 @@ class App extends Component {
|
|||||||
window.location.pathname.startsWith("/login") ||
|
window.location.pathname.startsWith("/login") ||
|
||||||
window.location.pathname.startsWith("/callback") ||
|
window.location.pathname.startsWith("/callback") ||
|
||||||
window.location.pathname.startsWith("/prompt") ||
|
window.location.pathname.startsWith("/prompt") ||
|
||||||
window.location.pathname.startsWith("/forget");
|
window.location.pathname.startsWith("/forget") ||
|
||||||
|
window.location.pathname.startsWith("/cas");
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPage() {
|
renderPage() {
|
||||||
@ -643,6 +656,8 @@ class App extends Component {
|
|||||||
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
|
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
|
||||||
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
|
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
|
||||||
|
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
|
||||||
|
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} />
|
||||||
<Route exact path="/callback" component={AuthCallback}/>
|
<Route exact path="/callback" component={AuthCallback}/>
|
||||||
<Route exact path="/callback/saml" component={SamlCallback}/>
|
<Route exact path="/callback/saml" component={SamlCallback}/>
|
||||||
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/>
|
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/>
|
||||||
|
@ -166,7 +166,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel("general:Logo", i18next.t("general:Logo - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
|
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
@ -17,3 +17,5 @@ export const GithubRepo = "https://github.com/casdoor/casdoor";
|
|||||||
|
|
||||||
export const ForceLanguage = "";
|
export const ForceLanguage = "";
|
||||||
export const DefaultLanguage = "en";
|
export const DefaultLanguage = "en";
|
||||||
|
|
||||||
|
export const EnableExtraPages = false;
|
||||||
|
@ -113,7 +113,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel("general:Favicon", i18next.t("general:Favicon - Tooltip"))} :
|
{Setting.getLabel( i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
@ -208,6 +208,18 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Master password"), i18next.t("general:Master password - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Master password"), i18next.t("general:Master password - Tooltip"))} :
|
||||||
|
@ -36,6 +36,7 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
PasswordSalt: "",
|
PasswordSalt: "",
|
||||||
phonePrefix: "86",
|
phonePrefix: "86",
|
||||||
defaultAvatar: "https://casbin.org/img/casbin.svg",
|
defaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||||
|
tags: [],
|
||||||
masterPassword: "",
|
masterPassword: "",
|
||||||
enableSoftDeletion: false,
|
enableSoftDeletion: false,
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ class PaymentEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:Type"), i18next.t("payment:Type - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.type} onChange={e => {
|
<Input value={this.state.payment.type} onChange={e => {
|
||||||
@ -122,20 +122,20 @@ class PaymentEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("payment:Good"), i18next.t("payment:Good - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:Product"), i18next.t("payment:Product - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.payment.good} onChange={e => {
|
<Input value={this.state.payment.productName} onChange={e => {
|
||||||
// this.updatePaymentField('good', e.target.value);
|
// this.updatePaymentField('productName', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("payment:Amount"), i18next.t("payment:Amount - Tooltip"))} :
|
{Setting.getLabel(i18next.t("payment:Price"), i18next.t("payment:Price - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<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);
|
// this.updatePaymentField('amount', e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
@ -150,6 +150,26 @@ class PaymentEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -34,10 +34,16 @@ class PaymentListPage extends BaseListPage {
|
|||||||
type: "PayPal",
|
type: "PayPal",
|
||||||
organization: "built-in",
|
organization: "built-in",
|
||||||
user: "admin",
|
user: "admin",
|
||||||
good: "A notebook computer",
|
productName: "computer-1",
|
||||||
amount: "300",
|
productDisplayName: "A notebook computer",
|
||||||
|
detail: "This is a computer with excellent CPU, memory and disk",
|
||||||
|
tag: "Promotion-1",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
|
price: 300.00,
|
||||||
|
payUrl: "https://pay.com/pay.php",
|
||||||
|
returnUrl: "https://door.casdoor.com/payments",
|
||||||
state: "Paid",
|
state: "Paid",
|
||||||
|
message: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,11 +78,11 @@ class PaymentListPage extends BaseListPage {
|
|||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Organization"),
|
title: i18next.t("general:Organization"),
|
||||||
dataIndex: 'owner',
|
dataIndex: 'organization',
|
||||||
key: 'owner',
|
key: 'organization',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('owner'),
|
...this.getColumnSearchProps('organization'),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/organizations/${text}`}>
|
<Link to={`/organizations/${text}`}>
|
||||||
@ -104,7 +110,7 @@ class PaymentListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Name"),
|
title: i18next.t("general:Name"),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
width: '150px',
|
width: '180px',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('name'),
|
...this.getColumnSearchProps('name'),
|
||||||
@ -151,10 +157,10 @@ class PaymentListPage extends BaseListPage {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:Type"),
|
title: i18next.t("payment:Type"),
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
key: 'type',
|
key: 'type',
|
||||||
width: '110px',
|
width: '140px',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
filterMultiple: false,
|
filterMultiple: false,
|
||||||
filters: Setting.getProviderTypeOptions('Payment').map((o) => {return {text:o.id, value:o.name}}),
|
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"),
|
title: i18next.t("payment:Product"),
|
||||||
dataIndex: 'good',
|
dataIndex: 'productDisplayName',
|
||||||
key: 'good',
|
key: 'productDisplayName',
|
||||||
width: '160px',
|
// width: '160px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('good'),
|
...this.getColumnSearchProps('productDisplayName'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("payment:Amount"),
|
title: i18next.t("payment:Price"),
|
||||||
dataIndex: 'amount',
|
dataIndex: 'price',
|
||||||
key: 'amount',
|
key: 'price',
|
||||||
width: '120px',
|
width: '120px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('amount'),
|
...this.getColumnSearchProps('price'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("payment:Currency"),
|
title: i18next.t("payment:Currency"),
|
||||||
@ -188,15 +194,24 @@ class PaymentListPage extends BaseListPage {
|
|||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('currency'),
|
...this.getColumnSearchProps('currency'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("payment:State"),
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
width: '120px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('state'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Action"),
|
title: i18next.t("general:Action"),
|
||||||
dataIndex: '',
|
dataIndex: '',
|
||||||
key: 'op',
|
key: 'op',
|
||||||
width: '170px',
|
width: '240px',
|
||||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<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>
|
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/payments/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={`Sure to delete payment: ${record.name} ?`}
|
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;
|
@ -13,11 +13,12 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Descriptions} from "antd";
|
import {Button, Descriptions, Spin} from "antd";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as ProductBackend from "./backend/ProductBackend";
|
import * as ProductBackend from "./backend/ProductBackend";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||||
import * as Provider from "./auth/Provider";
|
import * as Provider from "./auth/Provider";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
|
||||||
class ProductBuyPage extends React.Component {
|
class ProductBuyPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -27,6 +28,7 @@ class ProductBuyPage extends React.Component {
|
|||||||
productName: props.match?.params.productName,
|
productName: props.match?.params.productName,
|
||||||
product: null,
|
product: null,
|
||||||
providers: [],
|
providers: [],
|
||||||
|
isPlacingOrder: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +83,10 @@ class ProductBuyPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPrice(product) {
|
||||||
|
return `${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`;
|
||||||
|
}
|
||||||
|
|
||||||
getProviders(product) {
|
getProviders(product) {
|
||||||
if (this.state.providers.length === 0 || product.providers.length === 0) {
|
if (this.state.providers.length === 0 || product.providers.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
@ -107,6 +113,29 @@ class ProductBuyPage extends React.Component {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
getPayButton(provider) {
|
||||||
let text = provider.type;
|
let text = provider.type;
|
||||||
if (provider.type === "Alipay") {
|
if (provider.type === "Alipay") {
|
||||||
@ -131,11 +160,11 @@ class ProductBuyPage extends React.Component {
|
|||||||
renderProviderButton(provider, product) {
|
renderProviderButton(provider, product) {
|
||||||
return (
|
return (
|
||||||
<span key={provider.name} style={{width: "200px", marginRight: "20px", marginBottom: "10px"}}>
|
<span key={provider.name} style={{width: "200px", marginRight: "20px", marginBottom: "10px"}}>
|
||||||
<a style={{width: "200px"}} href={this.getPayUrl(product, provider)}>
|
<span style={{width: "200px", cursor: "pointer"}} onClick={() => this.buyProduct(product, provider)}>
|
||||||
{
|
{
|
||||||
this.getPayButton(provider)
|
this.getPayButton(provider)
|
||||||
}
|
}
|
||||||
</a>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -161,33 +190,41 @@ class ProductBuyPage extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const product = this.getProductObj();
|
const product = this.getProductObj();
|
||||||
|
|
||||||
|
if (product === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Descriptions title={i18next.t("product:Buy Product")} bordered>
|
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
|
||||||
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
|
<Descriptions title={i18next.t("product:Buy Product")} bordered>
|
||||||
|
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
|
||||||
<span style={{fontSize: 28}}>
|
<span style={{fontSize: 28}}>
|
||||||
{product?.displayName}
|
{product?.displayName}
|
||||||
</span>
|
</span>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:Detail")}><span style={{fontSize: 16}}>{product?.detail}</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: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:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:Image")} span={3}>
|
<Descriptions.Item label={i18next.t("product:Image")} span={3}>
|
||||||
<img src={product?.image} alt={product?.image} height={90} style={{marginBottom: '20px'}}/>
|
<img src={product?.image} alt={product?.image} height={90} style={{marginBottom: '20px'}}/>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:Price")}>
|
<Descriptions.Item label={i18next.t("product:Price")}>
|
||||||
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
|
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
|
||||||
{`${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`}
|
{
|
||||||
|
this.getPrice(product)
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</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:Sold")}><span style={{fontSize: 16}}>{product?.sold}</span></Descriptions.Item>
|
||||||
<Descriptions.Item label={i18next.t("product:Pay")} span={3}>
|
<Descriptions.Item label={i18next.t("product:Pay")} span={3}>
|
||||||
{
|
{
|
||||||
this.renderPay(product)
|
this.renderPay(product)
|
||||||
}
|
}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -212,6 +212,16 @@ class ProductEditPage extends React.Component {
|
|||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
|
||||||
|
@ -225,11 +225,12 @@ class ProductListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Action"),
|
title: i18next.t("general:Action"),
|
||||||
dataIndex: '',
|
dataIndex: '',
|
||||||
key: 'op',
|
key: 'op',
|
||||||
width: '170px',
|
width: '230px',
|
||||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<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>
|
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/products/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={`Sure to delete product: ${record.name} ?`}
|
title={`Sure to delete product: ${record.name} ?`}
|
||||||
|
@ -72,6 +72,8 @@ class ProviderEditPage extends React.Component {
|
|||||||
case "SMS":
|
case "SMS":
|
||||||
if (this.state.provider.type === "Volc Engine SMS")
|
if (this.state.provider.type === "Volc Engine SMS")
|
||||||
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
|
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:
|
default:
|
||||||
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
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":
|
case "SMS":
|
||||||
if (this.state.provider.type === "Volc Engine SMS")
|
if (this.state.provider.type === "Volc Engine SMS")
|
||||||
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
|
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:
|
default:
|
||||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
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") {
|
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
|
||||||
text = i18next.t("provider:SMS account");
|
text = i18next.t("provider:SMS account");
|
||||||
tooltip = i18next.t("provider:SMS account - Tooltip");
|
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 {
|
} else {
|
||||||
return null;
|
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'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
<Col style={{marginTop: '5px'}} span={2}>
|
<Col style={{marginTop: '5px'}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
|
@ -22,6 +22,7 @@ import copy from "copy-to-clipboard";
|
|||||||
import {authConfig} from "./auth/Auth";
|
import {authConfig} from "./auth/Auth";
|
||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import * as Conf from "./Conf";
|
||||||
|
|
||||||
export let ServerUrl = "";
|
export let ServerUrl = "";
|
||||||
|
|
||||||
@ -29,12 +30,17 @@ export let ServerUrl = "";
|
|||||||
export const StaticBaseUrl = "https://cdn.casbin.org";
|
export const StaticBaseUrl = "https://cdn.casbin.org";
|
||||||
|
|
||||||
// https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html
|
// https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html
|
||||||
export const CountryRegionData = getCountryRegionData()
|
export const CountryRegionData = getCountryRegionData();
|
||||||
|
|
||||||
export function getCountryRegionData() {
|
export function getCountryRegionData() {
|
||||||
|
let language = i18next.language;
|
||||||
|
if (language === null || language === "null") {
|
||||||
|
language = Conf.DefaultLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
var countries = require("i18n-iso-countries");
|
var countries = require("i18n-iso-countries");
|
||||||
countries.registerLocale(require("i18n-iso-countries/langs/" + i18next.language + ".json"));
|
countries.registerLocale(require("i18n-iso-countries/langs/" + language + ".json"));
|
||||||
var data = countries.getNames(i18next.language, {select: "official"});
|
var data = countries.getNames(language, {select: "official"});
|
||||||
var result = []
|
var result = []
|
||||||
for (var i in data)
|
for (var i in data)
|
||||||
result.push({code:i, name:data[i]})
|
result.push({code:i, name:data[i]})
|
||||||
@ -70,15 +76,7 @@ export function isProviderVisible(providerItem) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (providerItem.provider.type === "GitHub") {
|
return true;
|
||||||
if (isLocalhost()) {
|
|
||||||
return providerItem.provider.name.includes("localhost");
|
|
||||||
} else {
|
|
||||||
return !providerItem.provider.name.includes("localhost");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isProviderVisibleForSignUp(providerItem) {
|
export function isProviderVisibleForSignUp(providerItem) {
|
||||||
@ -404,6 +402,8 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'GitLab', name: 'GitLab'},
|
{id: 'GitLab', name: 'GitLab'},
|
||||||
{id: 'Adfs', name: 'Adfs'},
|
{id: 'Adfs', name: 'Adfs'},
|
||||||
{id: 'Baidu', name: 'Baidu'},
|
{id: 'Baidu', name: 'Baidu'},
|
||||||
|
{id: 'Alipay', name: 'Alipay'},
|
||||||
|
{id: 'Casdoor', name: 'Casdoor'},
|
||||||
{id: 'Infoflow', name: 'Infoflow'},
|
{id: 'Infoflow', name: 'Infoflow'},
|
||||||
{id: 'Apple', name: 'Apple'},
|
{id: 'Apple', name: 'Apple'},
|
||||||
{id: 'AzureAD', name: 'AzureAD'},
|
{id: 'AzureAD', name: 'AzureAD'},
|
||||||
@ -423,6 +423,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'Aliyun SMS', name: 'Aliyun SMS'},
|
{id: 'Aliyun SMS', name: 'Aliyun SMS'},
|
||||||
{id: 'Tencent Cloud SMS', name: 'Tencent Cloud SMS'},
|
{id: 'Tencent Cloud SMS', name: 'Tencent Cloud SMS'},
|
||||||
{id: 'Volc Engine SMS', name: 'Volc Engine SMS'},
|
{id: 'Volc Engine SMS', name: 'Volc Engine SMS'},
|
||||||
|
{id: 'Huawei Cloud SMS', name: 'Huawei Cloud SMS'},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (category === "Storage") {
|
} else if (category === "Storage") {
|
||||||
@ -444,6 +445,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'Alipay', name: 'Alipay'},
|
{id: 'Alipay', name: 'Alipay'},
|
||||||
{id: 'WeChat Pay', name: 'WeChat Pay'},
|
{id: 'WeChat Pay', name: 'WeChat Pay'},
|
||||||
{id: 'PayPal', name: 'PayPal'},
|
{id: 'PayPal', name: 'PayPal'},
|
||||||
|
{id: 'GC', name: 'GC'},
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
@ -42,7 +42,7 @@ class SyncerListPage extends BaseListPage {
|
|||||||
affiliationTable: "",
|
affiliationTable: "",
|
||||||
avatarBaseUrl: "",
|
avatarBaseUrl: "",
|
||||||
syncInterval: 10,
|
syncInterval: 10,
|
||||||
isEnabled: true,
|
isEnabled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +224,11 @@ class UserEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col style={{paddingRight: '20px'}} span={11} >
|
<Col style={{paddingRight: '20px'}} span={11} >
|
||||||
<Input value={this.state.user.email} disabled />
|
<Input value={this.state.user.email}
|
||||||
|
disabled={this.state.user.id === this.props.account?.id ? true : !Setting.isAdminUser(this.props.account)}
|
||||||
|
onChange={e => {
|
||||||
|
this.updateUserField('email', e.target.value);
|
||||||
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} >
|
<Col span={11} >
|
||||||
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
|
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
|
||||||
@ -235,7 +239,11 @@ class UserEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col style={{paddingRight: '20px'}} span={11} >
|
<Col style={{paddingRight: '20px'}} span={11} >
|
||||||
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`} disabled />
|
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
||||||
|
disabled={this.state.user.id === this.props.account?.id ? true : !Setting.isAdminUser(this.props.account)}
|
||||||
|
onChange={e => {
|
||||||
|
this.updateUserField('phone', e.target.value);
|
||||||
|
}}/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11} >
|
<Col span={11} >
|
||||||
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
|
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
|
||||||
@ -301,9 +309,24 @@ class UserEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<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>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: '20px'}} >
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
32
web/src/auth/AlipayLoginButton.js
Normal file
32
web/src/auth/AlipayLoginButton.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/alipay.svg`} alt="Sign in with Alipay" style={{width: 24, height: 24}} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
text: "Sign in with Alipay",
|
||||||
|
icon: Icon,
|
||||||
|
iconFormat: name => `fa fa-${name}`,
|
||||||
|
style: {background: "#ffffff", color: "#000000"},
|
||||||
|
activeStyle: {background: "#ededee"},
|
||||||
|
};
|
||||||
|
|
||||||
|
const AlipayLoginButton = createButton(config);
|
||||||
|
|
||||||
|
export default AlipayLoginButton;
|
@ -62,6 +62,14 @@ export function login(values, oAuthParams) {
|
|||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loginCas(values, params) {
|
||||||
|
return fetch(`${authConfig.serverUrl}/api/login?service=${params.service}`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(values),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
return fetch(`${authConfig.serverUrl}/api/logout`, {
|
return fetch(`${authConfig.serverUrl}/api/logout`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -106,6 +106,7 @@ class AuthCallback extends React.Component {
|
|||||||
method: method,
|
method: method,
|
||||||
};
|
};
|
||||||
const oAuthParams = Util.getOAuthGetParameters(innerParams);
|
const oAuthParams = Util.getOAuthGetParameters(innerParams);
|
||||||
|
const concatChar = oAuthParams?.redirectUri?.includes('?') ? '&' : '?';
|
||||||
AuthBackend.login(body, oAuthParams)
|
AuthBackend.login(body, oAuthParams)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 'ok') {
|
if (res.status === 'ok') {
|
||||||
@ -118,11 +119,11 @@ class AuthCallback extends React.Component {
|
|||||||
Setting.goToLink(link);
|
Setting.goToLink(link);
|
||||||
} else if (responseType === "code") {
|
} else if (responseType === "code") {
|
||||||
const code = res.data;
|
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}`);
|
// Util.showMessage("success", `Authorization code: ${res.data}`);
|
||||||
} else if (responseType === "token" || responseType === "id_token"){
|
} else if (responseType === "token" || responseType === "id_token"){
|
||||||
const token = res.data;
|
const token = res.data;
|
||||||
Setting.goToLink(`${oAuthParams.redirectUri}?${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
|
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
|
||||||
} else if (responseType === "link") {
|
} else if (responseType === "link") {
|
||||||
const from = innerParams.get("from");
|
const from = innerParams.get("from");
|
||||||
Setting.goToLinkSoft(this, from);
|
Setting.goToLinkSoft(this, from);
|
||||||
|
68
web/src/auth/CasLogout.js
Normal file
68
web/src/auth/CasLogout.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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 {Spin} from "antd";
|
||||||
|
import {withRouter} from "react-router-dom";
|
||||||
|
import * as AuthBackend from "./AuthBackend";
|
||||||
|
import * as Setting from "../Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
class CasLogout extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
msg: null,
|
||||||
|
};
|
||||||
|
if (props.match?.params.casApplicationName !== undefined) {
|
||||||
|
this.state.owner = props.match?.params.owner
|
||||||
|
this.state.applicationName = props.match?.params.casApplicationName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
const params = new URLSearchParams(this.props.location.search);
|
||||||
|
|
||||||
|
AuthBackend.logout()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 'ok') {
|
||||||
|
Setting.showMessage("success", `Logged out successfully`);
|
||||||
|
this.props.clearAccount()
|
||||||
|
let redirectUri = res.data2;
|
||||||
|
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||||
|
Setting.goToLink(redirectUri);
|
||||||
|
} else if (params.has("service")) {
|
||||||
|
Setting.goToLink(params.get("service"))
|
||||||
|
} else {
|
||||||
|
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div style={{textAlign: "center"}}>
|
||||||
|
{
|
||||||
|
<Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default withRouter(CasLogout);
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user