mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-14 16:13:24 +08:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
2023795f3c | |||
8d13bf7e27 | |||
29aa379fb2 | |||
7a95b9c1d5 | |||
0fc0ba0c76 | |||
24459d852e | |||
e3f5bf93b2 | |||
879ca6a488 | |||
544cd40a08 | |||
99f7883c7d | |||
88b0fb6e52 | |||
fa9b49e25b | |||
cd76e9372e | |||
04b9e05244 | |||
a78b2de7b2 | |||
d0952ae908 |
@ -179,6 +179,20 @@ func (c *ApiController) GetOAuthToken() {
|
||||
if clientId == "" && clientSecret == "" {
|
||||
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
|
||||
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host)
|
||||
@ -204,6 +218,18 @@ func (c *ApiController) RefreshToken() {
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
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.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"`
|
||||
}
|
@ -11,6 +11,8 @@ services:
|
||||
- db
|
||||
environment:
|
||||
RUNNING_IN_DOCKER: "true"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- ./conf:/conf/
|
||||
db:
|
||||
|
1
go.sum
1
go.sum
@ -381,6 +381,7 @@ 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-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-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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
|
||||
|
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
|
||||
}
|
@ -15,11 +15,13 @@
|
||||
package idp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@ -60,9 +62,38 @@ func (idp *GithubIdProvider) getConfig() *oauth2.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) {
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
|
||||
return idp.Config.Exchange(ctx, code)
|
||||
params := &struct {
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
//{
|
||||
@ -192,3 +223,30 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -231,6 +231,10 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
value.Add("code", code)
|
||||
}
|
||||
accessToken, err := idp.Session.Authorize(idp.Provider, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Get ExpiresAt's value
|
||||
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
|
||||
if valueOfExpire.IsValid() {
|
||||
@ -240,7 +244,8 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
AccessToken: accessToken,
|
||||
Expiry: expireAt,
|
||||
}
|
||||
return &token, err
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
|
@ -70,6 +70,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
||||
return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||
} else if typ == "Baidu" {
|
||||
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "Alipay" {
|
||||
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "Infoflow" {
|
||||
if subType == "Internal" {
|
||||
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
|
||||
|
@ -76,6 +76,9 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
|
||||
defer resp.Body.Close()
|
||||
tokenContent, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("token=(.*?)&")
|
||||
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
|
||||
@ -146,6 +149,9 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
|
||||
defer resp.Body.Close()
|
||||
openIdBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
|
||||
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
|
||||
@ -178,6 +184,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: openId,
|
||||
Username: qqUserInfo.Nickname,
|
||||
DisplayName: qqUserInfo.Nickname,
|
||||
AvatarUrl: qqUserInfo.FigureurlQq1,
|
||||
}
|
||||
|
@ -111,6 +111,7 @@ type WecomInternalUserInfo struct {
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar"`
|
||||
OpenId string `json:"open_userid"`
|
||||
UserId string `json:"userid"`
|
||||
}
|
||||
|
||||
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
@ -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)
|
||||
}
|
||||
userInfo := UserInfo{
|
||||
Id: infoResp.OpenId,
|
||||
Id: infoResp.UserId,
|
||||
Username: infoResp.Name,
|
||||
DisplayName: infoResp.Name,
|
||||
Email: infoResp.Email,
|
||||
|
@ -180,16 +180,15 @@ func CheckUserPassword(organization string, username string, password string) (*
|
||||
return nil, "the user is forbidden to sign in, please contact the administrator"
|
||||
}
|
||||
|
||||
msg := CheckPassword(user, password)
|
||||
if msg != "" {
|
||||
//for ldap users
|
||||
if user.Ldap != "" {
|
||||
return checkLdapUserPassword(user, password)
|
||||
if user.Ldap != "" {
|
||||
//ONLY for ldap users
|
||||
return checkLdapUserPassword(user, password)
|
||||
} else {
|
||||
msg := CheckPassword(user, password)
|
||||
if msg != "" {
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return user, ""
|
||||
}
|
||||
|
||||
|
@ -21,17 +21,19 @@ import (
|
||||
)
|
||||
|
||||
func InitDb() {
|
||||
initBuiltInOrganization()
|
||||
initBuiltInUser()
|
||||
initBuiltInApplication()
|
||||
initBuiltInCert()
|
||||
initBuiltInLdap()
|
||||
existed := initBuiltInOrganization()
|
||||
if !existed {
|
||||
initBuiltInUser()
|
||||
initBuiltInApplication()
|
||||
initBuiltInCert()
|
||||
initBuiltInLdap()
|
||||
}
|
||||
}
|
||||
|
||||
func initBuiltInOrganization() {
|
||||
func initBuiltInOrganization() bool {
|
||||
organization := getOrganization("admin", "built-in")
|
||||
if organization != nil {
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
organization = &Organization{
|
||||
@ -47,6 +49,7 @@ func initBuiltInOrganization() {
|
||||
Tags: []string{},
|
||||
}
|
||||
AddOrganization(organization)
|
||||
return false
|
||||
}
|
||||
|
||||
func initBuiltInUser() {
|
||||
|
@ -33,7 +33,7 @@ type Provider struct {
|
||||
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
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"`
|
||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
|
@ -34,6 +34,9 @@ func (application *Application) GetProviderItem(providerName string) *ProviderIt
|
||||
}
|
||||
|
||||
func (pi *ProviderItem) IsProviderVisible() bool {
|
||||
if pi.Provider == nil {
|
||||
return false
|
||||
}
|
||||
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
|
||||
}
|
||||
|
||||
|
@ -439,14 +439,15 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
TokenType: "Bearer",
|
||||
}
|
||||
AddToken(newToken)
|
||||
DeleteToken(&token)
|
||||
|
||||
tokenWrapper := &TokenWrapper{
|
||||
AccessToken: token.AccessToken,
|
||||
IdToken: token.AccessToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
TokenType: token.TokenType,
|
||||
ExpiresIn: token.ExpiresIn,
|
||||
Scope: token.Scope,
|
||||
AccessToken: newToken.AccessToken,
|
||||
IdToken: newToken.AccessToken,
|
||||
RefreshToken: newToken.RefreshToken,
|
||||
TokenType: newToken.TokenType,
|
||||
ExpiresIn: newToken.ExpiresIn,
|
||||
Scope: newToken.Scope,
|
||||
}
|
||||
|
||||
return tokenWrapper
|
||||
@ -521,7 +522,8 @@ func GetPasswordToken(application *Application, username string, password string
|
||||
if user == nil {
|
||||
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")
|
||||
}
|
||||
if user.IsForbidden {
|
||||
|
@ -85,6 +85,7 @@ type User struct {
|
||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
||||
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
|
||||
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
||||
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||
@ -304,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"}
|
||||
}
|
||||
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)
|
||||
|
@ -83,6 +83,10 @@ class ProductBuyPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getPrice(product) {
|
||||
return `${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`;
|
||||
}
|
||||
|
||||
getProviders(product) {
|
||||
if (this.state.providers.length === 0 || product.providers.length === 0) {
|
||||
return [];
|
||||
@ -207,7 +211,9 @@ class ProductBuyPage extends React.Component {
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:Price")}>
|
||||
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
|
||||
{`${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`}
|
||||
{
|
||||
this.getPrice(product)
|
||||
}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</span></Descriptions.Item>
|
||||
|
@ -22,6 +22,7 @@ import copy from "copy-to-clipboard";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
import {Helmet} from "react-helmet";
|
||||
import moment from "moment";
|
||||
import * as Conf from "./Conf";
|
||||
|
||||
export let ServerUrl = "";
|
||||
|
||||
@ -29,12 +30,17 @@ export let ServerUrl = "";
|
||||
export const StaticBaseUrl = "https://cdn.casbin.org";
|
||||
|
||||
// https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html
|
||||
export const CountryRegionData = getCountryRegionData()
|
||||
export const CountryRegionData = getCountryRegionData();
|
||||
|
||||
export function getCountryRegionData() {
|
||||
let language = i18next.language;
|
||||
if (language === null || language === "null") {
|
||||
language = Conf.DefaultLanguage;
|
||||
}
|
||||
|
||||
var countries = require("i18n-iso-countries");
|
||||
countries.registerLocale(require("i18n-iso-countries/langs/" + i18next.language + ".json"));
|
||||
var data = countries.getNames(i18next.language, {select: "official"});
|
||||
countries.registerLocale(require("i18n-iso-countries/langs/" + language + ".json"));
|
||||
var data = countries.getNames(language, {select: "official"});
|
||||
var result = []
|
||||
for (var i in data)
|
||||
result.push({code:i, name:data[i]})
|
||||
@ -396,6 +402,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: 'GitLab', name: 'GitLab'},
|
||||
{id: 'Adfs', name: 'Adfs'},
|
||||
{id: 'Baidu', name: 'Baidu'},
|
||||
{id: 'Alipay', name: 'Alipay'},
|
||||
{id: 'Casdoor', name: 'Casdoor'},
|
||||
{id: 'Infoflow', name: 'Infoflow'},
|
||||
{id: 'Apple', name: 'Apple'},
|
||||
|
@ -42,7 +42,7 @@ class SyncerListPage extends BaseListPage {
|
||||
affiliationTable: "",
|
||||
avatarBaseUrl: "",
|
||||
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"))} :
|
||||
</Col>
|
||||
<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 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}
|
||||
@ -235,7 +239,11 @@ class UserEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
||||
</Col>
|
||||
<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 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}
|
||||
|
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;
|
@ -36,6 +36,7 @@ import LarkLoginButton from "./LarkLoginButton";
|
||||
import GitLabLoginButton from "./GitLabLoginButton";
|
||||
import AdfsLoginButton from "./AdfsLoginButton";
|
||||
import BaiduLoginButton from "./BaiduLoginButton";
|
||||
import AlipayLoginButton from "./AlipayLoginButton";
|
||||
import CasdoorLoginButton from "./CasdoorLoginButton";
|
||||
import InfoflowLoginButton from "./InfoflowLoginButton";
|
||||
import AppleLoginButton from "./AppleLoginButton"
|
||||
@ -206,6 +207,8 @@ class LoginPage extends React.Component {
|
||||
return <CasdoorLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Baidu") {
|
||||
return <BaiduLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Alipay") {
|
||||
return <AlipayLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Infoflow") {
|
||||
return <InfoflowLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Apple") {
|
||||
|
@ -78,6 +78,10 @@ const authInfo = {
|
||||
scope: "basic",
|
||||
endpoint: "http://openapi.baidu.com/oauth/2.0/authorize",
|
||||
},
|
||||
Alipay: {
|
||||
scope: "basic",
|
||||
endpoint: "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm",
|
||||
},
|
||||
Casdoor: {
|
||||
scope: "openid%20profile%20email",
|
||||
endpoint: "http://example.com",
|
||||
@ -287,6 +291,8 @@ export function getAuthUrl(application, provider, method) {
|
||||
return `${provider.domain}/adfs/oauth2/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&nonce=casdoor&scope=openid`;
|
||||
} else if (provider.type === "Baidu") {
|
||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&display=popup`;
|
||||
} else if (provider.type === "Alipay") {
|
||||
return `${endpoint}?app_id=${provider.clientId}&scope=auth_user&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&display=popup`;
|
||||
} else if (provider.type === "Casdoor") {
|
||||
return `${provider.domain}/login/oauth/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||
} else if (provider.type === "Infoflow"){
|
||||
|
Reference in New Issue
Block a user