mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-10 01:56:49 +08:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
976b5766a5 | |||
a92d20162a | |||
204b1c2b8c | |||
49fb269170 | |||
c532a5d54d | |||
89df80baca | |||
d988ac814c | |||
e4b25055d5 |
@ -161,6 +161,11 @@ func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
} else if urlPath == "/api/upload-resource" {
|
||||||
|
if subOwner == "app" && subName == "app-casibase" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,15 @@ func (c *ApiController) GetResources() {
|
|||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
userObj, ok := c.RequireSignedInUser()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if userObj != nil && userObj.IsAdmin {
|
||||||
|
user = ""
|
||||||
|
}
|
||||||
|
|
||||||
if sortField == "Direct" {
|
if sortField == "Direct" {
|
||||||
provider, err := c.GetProviderFromContext("Storage")
|
provider, err := c.GetProviderFromContext("Storage")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -273,7 +273,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
// base64 decode
|
// base64 decode
|
||||||
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", method, fmt.Errorf("err: Failed to decode SAML request , %s", err.Error())
|
return "", "", "", fmt.Errorf("err: Failed to decode SAML request, %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// decompress
|
// decompress
|
||||||
@ -281,7 +281,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
rdr := flate.NewReader(bytes.NewReader(defated))
|
rdr := flate.NewReader(bytes.NewReader(defated))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, err := io.CopyN(&buffer, rdr, 1024)
|
_, err = io.CopyN(&buffer, rdr, 1024)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
@ -293,12 +293,12 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
var authnRequest saml.AuthNRequest
|
var authnRequest saml.AuthNRequest
|
||||||
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
|
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", method, fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request. %s", err.Error())
|
return "", "", "", fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request, %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify samlRequest
|
// verify samlRequest
|
||||||
if isValid := application.IsRedirectUriValid(authnRequest.Issuer); !isValid {
|
if isValid := application.IsRedirectUriValid(authnRequest.Issuer); !isValid {
|
||||||
return "", "", method, fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer)
|
return "", "", "", fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get certificate string
|
// get certificate string
|
||||||
@ -323,8 +323,13 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
// build signedResponse
|
// build signedResponse
|
||||||
samlResponse, _ := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer, authnRequest.ID, application.RedirectUris)
|
samlResponse, err := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer, authnRequest.ID, application.RedirectUris)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", fmt.Errorf("err: NewSamlResponse() error, %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
randomKeyStore := &X509Key{
|
randomKeyStore := &X509Key{
|
||||||
PrivateKey: cert.PrivateKey,
|
PrivateKey: cert.PrivateKey,
|
||||||
X509Certificate: certificate,
|
X509Certificate: certificate,
|
||||||
@ -336,18 +341,23 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
ctx.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("")
|
ctx.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("")
|
||||||
}
|
}
|
||||||
|
|
||||||
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
|
// signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
|
||||||
//if err != nil {
|
// if err != nil {
|
||||||
// return "", "", fmt.Errorf("err: %s", err.Error())
|
// return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
//}
|
// }
|
||||||
|
|
||||||
sig, err := ctx.ConstructSignature(samlResponse, true)
|
sig, err := ctx.ConstructSignature(samlResponse, true)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", fmt.Errorf("err: Failed to serializes the SAML request into bytes, %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
samlResponse.InsertChildAt(1, sig)
|
samlResponse.InsertChildAt(1, sig)
|
||||||
|
|
||||||
doc := etree.NewDocument()
|
doc := etree.NewDocument()
|
||||||
doc.SetRoot(samlResponse)
|
doc.SetRoot(samlResponse)
|
||||||
xmlBytes, err := doc.WriteToBytes()
|
xmlBytes, err := doc.WriteToBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", method, fmt.Errorf("err: Failed to serializes the SAML request into bytes, %s", err.Error())
|
return "", "", "", fmt.Errorf("err: Failed to serializes the SAML request into bytes, %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// compress
|
// compress
|
||||||
@ -355,16 +365,19 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
flated := bytes.NewBuffer(nil)
|
flated := bytes.NewBuffer(nil)
|
||||||
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
|
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", method, err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = writer.Write(xmlBytes)
|
_, err = writer.Write(xmlBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writer.Close()
|
err = writer.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
xmlBytes = flated.Bytes()
|
xmlBytes = flated.Bytes()
|
||||||
}
|
}
|
||||||
// base64 encode
|
// base64 encode
|
||||||
@ -373,12 +386,12 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
||||||
func NewSamlResponse11(user *User, requestID string, host string) *etree.Element {
|
func NewSamlResponse11(user *User, requestID string, host string) (*etree.Element, error) {
|
||||||
samlResponse := &etree.Element{
|
samlResponse := &etree.Element{
|
||||||
Space: "samlp",
|
Space: "samlp",
|
||||||
Tag: "Response",
|
Tag: "Response",
|
||||||
}
|
}
|
||||||
// create samlresponse
|
|
||||||
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:1.0:protocol")
|
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:1.0:protocol")
|
||||||
samlResponse.CreateAttr("MajorVersion", "1")
|
samlResponse.CreateAttr("MajorVersion", "1")
|
||||||
samlResponse.CreateAttr("MinorVersion", "1")
|
samlResponse.CreateAttr("MinorVersion", "1")
|
||||||
@ -431,11 +444,15 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
|
|||||||
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
||||||
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
||||||
|
|
||||||
data, _ := json.Marshal(user)
|
data, err := json.Marshal(user)
|
||||||
tmp := map[string]string{}
|
|
||||||
err := json.Unmarshal(data, &tmp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := map[string]string{}
|
||||||
|
err = json.Unmarshal(data, &tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range tmp {
|
for k, v := range tmp {
|
||||||
@ -447,7 +464,7 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return samlResponse
|
return samlResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSamlRedirectAddress(owner string, application string, relayState string, samlRequest string, host string) string {
|
func GetSamlRedirectAddress(owner string, application string, relayState string, samlRequest string, host string) string {
|
||||||
|
705
object/token.go
705
object/token.go
@ -16,33 +16,13 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/i18n"
|
|
||||||
"github.com/casdoor/casdoor/idp"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
hourSeconds = int(time.Hour / time.Second)
|
|
||||||
InvalidRequest = "invalid_request"
|
|
||||||
InvalidClient = "invalid_client"
|
|
||||||
InvalidGrant = "invalid_grant"
|
|
||||||
UnauthorizedClient = "unauthorized_client"
|
|
||||||
UnsupportedGrantType = "unsupported_grant_type"
|
|
||||||
InvalidScope = "invalid_scope"
|
|
||||||
EndpointError = "endpoint_error"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Code struct {
|
|
||||||
Message string `xorm:"varchar(100)" json:"message"`
|
|
||||||
Code string `xorm:"varchar(100)" json:"code"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@ -65,35 +45,6 @@ type Token struct {
|
|||||||
CodeExpireIn int64 `json:"codeExpireIn"`
|
CodeExpireIn int64 `json:"codeExpireIn"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenWrapper struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
IdToken string `json:"id_token"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
TokenType string `json:"token_type"`
|
|
||||||
ExpiresIn int `json:"expires_in"`
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenError struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
ErrorDescription string `json:"error_description,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type IntrospectionResponse struct {
|
|
||||||
Active bool `json:"active"`
|
|
||||||
Scope string `json:"scope,omitempty"`
|
|
||||||
ClientId string `json:"client_id,omitempty"`
|
|
||||||
Username string `json:"username,omitempty"`
|
|
||||||
TokenType string `json:"token_type,omitempty"`
|
|
||||||
Exp int64 `json:"exp,omitempty"`
|
|
||||||
Iat int64 `json:"iat,omitempty"`
|
|
||||||
Nbf int64 `json:"nbf,omitempty"`
|
|
||||||
Sub string `json:"sub,omitempty"`
|
|
||||||
Aud []string `json:"aud,omitempty"`
|
|
||||||
Iss string `json:"iss,omitempty"`
|
|
||||||
Jti string `json:"jti,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTokenCount(owner, organization, field, value string) (int64, error) {
|
func GetTokenCount(owner, organization, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Token{Organization: organization})
|
return session.Count(&Token{Organization: organization})
|
||||||
@ -279,659 +230,3 @@ func DeleteToken(token *Token) (bool, error) {
|
|||||||
|
|
||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
|
||||||
token, err := GetTokenByAccessToken(accessToken)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
if token == nil {
|
|
||||||
return false, nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token.ExpiresIn = 0
|
|
||||||
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(token)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
application, err := getApplication(token.Owner, token.Application)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return affected != 0, application, token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string, lang string) (string, *Application, error) {
|
|
||||||
if responseType != "code" && responseType != "token" && responseType != "id_token" {
|
|
||||||
return fmt.Sprintf(i18n.Translate(lang, "token:Grant_type: %s is not supported in this application"), responseType), nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
application, err := GetApplicationByClientId(clientId)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if application == nil {
|
|
||||||
return i18n.Translate(lang, "token:Invalid client_id"), nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !application.IsRedirectUriValid(redirectUri) {
|
|
||||||
return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mask application for /api/get-app-login
|
|
||||||
application.ClientSecret = ""
|
|
||||||
return "", application, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) (*Code, error) {
|
|
||||||
user, err := GetUser(userId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user == nil {
|
|
||||||
return &Code{
|
|
||||||
Message: fmt.Sprintf("general:The user: %s doesn't exist", userId),
|
|
||||||
Code: "",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
if user.IsForbidden {
|
|
||||||
return &Code{
|
|
||||||
Message: "error: the user is forbidden to sign in, please contact the administrator",
|
|
||||||
Code: "",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, application, err := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state, lang)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg != "" {
|
|
||||||
return &Code{
|
|
||||||
Message: msg,
|
|
||||||
Code: "",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ExtendUserWithRolesAndPermissions(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if challenge == "null" {
|
|
||||||
challenge = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
token := &Token{
|
|
||||||
Owner: application.Owner,
|
|
||||||
Name: tokenName,
|
|
||||||
CreatedTime: util.GetCurrentTime(),
|
|
||||||
Application: application.Name,
|
|
||||||
Organization: user.Owner,
|
|
||||||
User: user.Name,
|
|
||||||
Code: util.GenerateClientId(),
|
|
||||||
AccessToken: accessToken,
|
|
||||||
RefreshToken: refreshToken,
|
|
||||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
|
||||||
Scope: scope,
|
|
||||||
TokenType: "Bearer",
|
|
||||||
CodeChallenge: challenge,
|
|
||||||
CodeIsUsed: false,
|
|
||||||
CodeExpireIn: time.Now().Add(time.Minute * 5).Unix(),
|
|
||||||
}
|
|
||||||
_, err = AddToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Code{
|
|
||||||
Message: "",
|
|
||||||
Code: token.Code,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string) (interface{}, error) {
|
|
||||||
application, err := GetApplicationByClientId(clientId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if application == nil {
|
|
||||||
return &TokenError{
|
|
||||||
Error: InvalidClient,
|
|
||||||
ErrorDescription: "client_id is invalid",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if grantType is allowed in the current application
|
|
||||||
|
|
||||||
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
|
|
||||||
return &TokenError{
|
|
||||||
Error: UnsupportedGrantType,
|
|
||||||
ErrorDescription: fmt.Sprintf("grant_type: %s is not supported in this application", grantType),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var token *Token
|
|
||||||
var tokenError *TokenError
|
|
||||||
switch grantType {
|
|
||||||
case "authorization_code": // Authorization Code Grant
|
|
||||||
token, tokenError, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
|
|
||||||
case "password": // Resource Owner Password Credentials Grant
|
|
||||||
token, tokenError, err = GetPasswordToken(application, username, password, scope, host)
|
|
||||||
case "client_credentials": // Client Credentials Grant
|
|
||||||
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
|
||||||
case "refresh_token":
|
|
||||||
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return refreshToken2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag == "wechat_miniprogram" {
|
|
||||||
// Wechat Mini Program
|
|
||||||
token, tokenError, err = GetWechatMiniProgramToken(application, code, host, username, avatar, lang)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokenError != nil {
|
|
||||||
return tokenError, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token.CodeIsUsed = true
|
|
||||||
|
|
||||||
go updateUsedByCode(token)
|
|
||||||
|
|
||||||
tokenWrapper := &TokenWrapper{
|
|
||||||
AccessToken: token.AccessToken,
|
|
||||||
IdToken: token.AccessToken,
|
|
||||||
RefreshToken: token.RefreshToken,
|
|
||||||
TokenType: token.TokenType,
|
|
||||||
ExpiresIn: token.ExpiresIn,
|
|
||||||
Scope: token.Scope,
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenWrapper, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) (interface{}, error) {
|
|
||||||
// check parameters
|
|
||||||
if grantType != "refresh_token" {
|
|
||||||
return &TokenError{
|
|
||||||
Error: UnsupportedGrantType,
|
|
||||||
ErrorDescription: "grant_type should be refresh_token",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
application, err := GetApplicationByClientId(clientId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if application == nil {
|
|
||||||
return &TokenError{
|
|
||||||
Error: InvalidClient,
|
|
||||||
ErrorDescription: "client_id is invalid",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientSecret != "" && application.ClientSecret != clientSecret {
|
|
||||||
return &TokenError{
|
|
||||||
Error: InvalidClient,
|
|
||||||
ErrorDescription: "client_secret is invalid",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check whether the refresh token is valid, and has not expired.
|
|
||||||
token, err := GetTokenByRefreshToken(refreshToken)
|
|
||||||
if err != nil || token == nil {
|
|
||||||
return &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "refresh token is invalid, expired or revoked",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := getCertByApplication(application)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if cert == nil {
|
|
||||||
return &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: fmt.Sprintf("cert: %s cannot be found", application.Cert),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = ParseJwtToken(refreshToken, cert)
|
|
||||||
if err != nil {
|
|
||||||
return &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate a new token
|
|
||||||
user, err := getUser(application.Organization, token.User)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.IsForbidden {
|
|
||||||
return &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ExtendUserWithRolesAndPermissions(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
|
||||||
if err != nil {
|
|
||||||
return &TokenError{
|
|
||||||
Error: EndpointError,
|
|
||||||
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newToken := &Token{
|
|
||||||
Owner: application.Owner,
|
|
||||||
Name: tokenName,
|
|
||||||
CreatedTime: util.GetCurrentTime(),
|
|
||||||
Application: application.Name,
|
|
||||||
Organization: user.Owner,
|
|
||||||
User: user.Name,
|
|
||||||
Code: util.GenerateClientId(),
|
|
||||||
AccessToken: newAccessToken,
|
|
||||||
RefreshToken: newRefreshToken,
|
|
||||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
|
||||||
Scope: scope,
|
|
||||||
TokenType: "Bearer",
|
|
||||||
}
|
|
||||||
_, err = AddToken(newToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = DeleteToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenWrapper := &TokenWrapper{
|
|
||||||
AccessToken: newToken.AccessToken,
|
|
||||||
IdToken: newToken.AccessToken,
|
|
||||||
RefreshToken: newToken.RefreshToken,
|
|
||||||
TokenType: newToken.TokenType,
|
|
||||||
ExpiresIn: newToken.ExpiresIn,
|
|
||||||
Scope: newToken.Scope,
|
|
||||||
}
|
|
||||||
return tokenWrapper, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PkceChallenge: base64-URL-encoded SHA256 hash of verifier, per rfc 7636
|
|
||||||
func pkceChallenge(verifier string) string {
|
|
||||||
sum := sha256.Sum256([]byte(verifier))
|
|
||||||
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
|
|
||||||
return challenge
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsGrantTypeValid
|
|
||||||
// Check if grantType is allowed in the current application
|
|
||||||
// authorization_code is allowed by default
|
|
||||||
func IsGrantTypeValid(method string, grantTypes []string) bool {
|
|
||||||
if method == "authorization_code" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, m := range grantTypes {
|
|
||||||
if m == method {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAuthorizationCodeToken
|
|
||||||
// Authorization code flow
|
|
||||||
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, *TokenError, error) {
|
|
||||||
if code == "" {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidRequest,
|
|
||||||
ErrorDescription: "authorization code should not be empty",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := getTokenByCode(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if token == nil {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "authorization code is invalid",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
if token.CodeIsUsed {
|
|
||||||
// anti replay attacks
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "authorization code has been used",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "verifier is invalid",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if application.ClientSecret != clientSecret {
|
|
||||||
// when using PKCE, the Client Secret can be empty,
|
|
||||||
// but if it is provided, it must be accurate.
|
|
||||||
if token.CodeChallenge == "" {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidClient,
|
|
||||||
ErrorDescription: "client_secret is invalid",
|
|
||||||
}, nil
|
|
||||||
} else {
|
|
||||||
if clientSecret != "" {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidClient,
|
|
||||||
ErrorDescription: "client_secret is invalid",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if application.Name != token.Application {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "the token is for wrong application (client_id)",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if time.Now().Unix() > token.CodeExpireIn {
|
|
||||||
// code must be used within 5 minutes
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "authorization code has expired",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return token, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPasswordToken
|
|
||||||
// Resource Owner Password Credentials flow
|
|
||||||
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError, error) {
|
|
||||||
user, err := GetUserByFields(application.Organization, username)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if user == nil {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "the user does not exist",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Ldap != "" {
|
|
||||||
err = checkLdapUserPassword(user, password, "en")
|
|
||||||
} else {
|
|
||||||
err = CheckPassword(user, password, "en")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: fmt.Sprintf("invalid username or password: %s", err.Error()),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.IsForbidden {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ExtendUserWithRolesAndPermissions(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: EndpointError,
|
|
||||||
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
token := &Token{
|
|
||||||
Owner: application.Owner,
|
|
||||||
Name: tokenName,
|
|
||||||
CreatedTime: util.GetCurrentTime(),
|
|
||||||
Application: application.Name,
|
|
||||||
Organization: user.Owner,
|
|
||||||
User: user.Name,
|
|
||||||
Code: util.GenerateClientId(),
|
|
||||||
AccessToken: accessToken,
|
|
||||||
RefreshToken: refreshToken,
|
|
||||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
|
||||||
Scope: scope,
|
|
||||||
TokenType: "Bearer",
|
|
||||||
CodeIsUsed: true,
|
|
||||||
}
|
|
||||||
_, err = AddToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClientCredentialsToken
|
|
||||||
// Client Credentials flow
|
|
||||||
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, *TokenError, error) {
|
|
||||||
if application.ClientSecret != clientSecret {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidClient,
|
|
||||||
ErrorDescription: "client_secret is invalid",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
nullUser := &User{
|
|
||||||
Owner: application.Owner,
|
|
||||||
Id: application.GetId(),
|
|
||||||
Name: application.Name,
|
|
||||||
Type: "application",
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", scope, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: EndpointError,
|
|
||||||
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
token := &Token{
|
|
||||||
Owner: application.Owner,
|
|
||||||
Name: tokenName,
|
|
||||||
CreatedTime: util.GetCurrentTime(),
|
|
||||||
Application: application.Name,
|
|
||||||
Organization: application.Organization,
|
|
||||||
User: nullUser.Name,
|
|
||||||
Code: util.GenerateClientId(),
|
|
||||||
AccessToken: accessToken,
|
|
||||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
|
||||||
Scope: scope,
|
|
||||||
TokenType: "Bearer",
|
|
||||||
CodeIsUsed: true,
|
|
||||||
}
|
|
||||||
_, err = AddToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTokenByUser
|
|
||||||
// Implicit flow
|
|
||||||
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
|
|
||||||
err := ExtendUserWithRolesAndPermissions(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
token := &Token{
|
|
||||||
Owner: application.Owner,
|
|
||||||
Name: tokenName,
|
|
||||||
CreatedTime: util.GetCurrentTime(),
|
|
||||||
Application: application.Name,
|
|
||||||
Organization: user.Owner,
|
|
||||||
User: user.Name,
|
|
||||||
Code: util.GenerateClientId(),
|
|
||||||
AccessToken: accessToken,
|
|
||||||
RefreshToken: refreshToken,
|
|
||||||
ExpiresIn: application.ExpireInHours * hourSeconds,
|
|
||||||
Scope: scope,
|
|
||||||
TokenType: "Bearer",
|
|
||||||
CodeIsUsed: true,
|
|
||||||
}
|
|
||||||
_, err = AddToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWechatMiniProgramToken
|
|
||||||
// Wechat Mini Program flow
|
|
||||||
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string, lang string) (*Token, *TokenError, error) {
|
|
||||||
mpProvider := GetWechatMiniProgramProvider(application)
|
|
||||||
if mpProvider == nil {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidClient,
|
|
||||||
ErrorDescription: "the application does not support wechat mini program",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
provider, err := GetProvider(util.GetId("admin", mpProvider.Name))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
|
|
||||||
session, err := mpIdp.GetSessionByCode(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: fmt.Sprintf("get wechat mini program session error: %s", err.Error()),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
openId, unionId := session.Openid, session.Unionid
|
|
||||||
if openId == "" && unionId == "" {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidRequest,
|
|
||||||
ErrorDescription: "the wechat mini program session is invalid",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
user, err := getUserByWechatId(application.Organization, openId, unionId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user == nil {
|
|
||||||
if !application.EnableSignUp {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: InvalidGrant,
|
|
||||||
ErrorDescription: "the application does not allow to sign up new account",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
// Add new user
|
|
||||||
var name string
|
|
||||||
if CheckUsername(username, lang) == "" {
|
|
||||||
name = username
|
|
||||||
} else {
|
|
||||||
name = fmt.Sprintf("wechat-%s", openId)
|
|
||||||
}
|
|
||||||
|
|
||||||
user = &User{
|
|
||||||
Owner: application.Organization,
|
|
||||||
Id: util.GenerateId(),
|
|
||||||
Name: name,
|
|
||||||
Avatar: avatar,
|
|
||||||
SignupApplication: application.Name,
|
|
||||||
WeChat: openId,
|
|
||||||
Type: "normal-user",
|
|
||||||
CreatedTime: util.GetCurrentTime(),
|
|
||||||
IsAdmin: false,
|
|
||||||
IsForbidden: false,
|
|
||||||
IsDeleted: false,
|
|
||||||
Properties: map[string]string{
|
|
||||||
UserPropertiesWechatOpenId: openId,
|
|
||||||
UserPropertiesWechatUnionId: unionId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err = AddUser(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ExtendUserWithRolesAndPermissions(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &TokenError{
|
|
||||||
Error: EndpointError,
|
|
||||||
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token := &Token{
|
|
||||||
Owner: application.Owner,
|
|
||||||
Name: tokenName,
|
|
||||||
CreatedTime: util.GetCurrentTime(),
|
|
||||||
Application: application.Name,
|
|
||||||
Organization: user.Owner,
|
|
||||||
User: user.Name,
|
|
||||||
Code: session.SessionKey, // a trick, because miniprogram does not use the code, so use the code field to save the session_key
|
|
||||||
AccessToken: accessToken,
|
|
||||||
RefreshToken: refreshToken,
|
|
||||||
ExpiresIn: application.ExpireInHours * 60,
|
|
||||||
Scope: "",
|
|
||||||
TokenType: "Bearer",
|
|
||||||
CodeIsUsed: true,
|
|
||||||
}
|
|
||||||
_, err = AddToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return token, nil, nil
|
|
||||||
}
|
|
||||||
|
@ -256,7 +256,7 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
|
|||||||
|
|
||||||
ticket := request.AssertionArtifact.InnerXML
|
ticket := request.AssertionArtifact.InnerXML
|
||||||
if ticket == "" {
|
if ticket == "" {
|
||||||
return "", "", fmt.Errorf("samlp:AssertionArtifact field not found")
|
return "", "", fmt.Errorf("request.AssertionArtifact.InnerXML error, AssertionArtifact field not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, _, service, userId := GetCasTokenByTicket(ticket)
|
ok, _, service, userId := GetCasTokenByTicket(ticket)
|
||||||
@ -282,7 +282,10 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
|
|||||||
return "", "", fmt.Errorf("application for user %s found", userId)
|
return "", "", fmt.Errorf("application for user %s found", userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
samlResponse := NewSamlResponse11(user, request.RequestID, host)
|
samlResponse, err := NewSamlResponse11(user, request.RequestID, host)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
cert, err := getCertByApplication(application)
|
cert, err := getCertByApplication(application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
728
object/token_oauth.go
Normal file
728
object/token_oauth.go
Normal file
@ -0,0 +1,728 @@
|
|||||||
|
// Copyright 2024 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 (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/i18n"
|
||||||
|
"github.com/casdoor/casdoor/idp"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/xorm-io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hourSeconds = int(time.Hour / time.Second)
|
||||||
|
InvalidRequest = "invalid_request"
|
||||||
|
InvalidClient = "invalid_client"
|
||||||
|
InvalidGrant = "invalid_grant"
|
||||||
|
UnauthorizedClient = "unauthorized_client"
|
||||||
|
UnsupportedGrantType = "unsupported_grant_type"
|
||||||
|
InvalidScope = "invalid_scope"
|
||||||
|
EndpointError = "endpoint_error"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Code struct {
|
||||||
|
Message string `xorm:"varchar(100)" json:"message"`
|
||||||
|
Code string `xorm:"varchar(100)" json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenWrapper struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
IdToken string `json:"id_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenError struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorDescription string `json:"error_description,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntrospectionResponse struct {
|
||||||
|
Active bool `json:"active"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
ClientId string `json:"client_id,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
TokenType string `json:"token_type,omitempty"`
|
||||||
|
Exp int64 `json:"exp,omitempty"`
|
||||||
|
Iat int64 `json:"iat,omitempty"`
|
||||||
|
Nbf int64 `json:"nbf,omitempty"`
|
||||||
|
Sub string `json:"sub,omitempty"`
|
||||||
|
Aud []string `json:"aud,omitempty"`
|
||||||
|
Iss string `json:"iss,omitempty"`
|
||||||
|
Jti string `json:"jti,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
||||||
|
token, err := GetTokenByAccessToken(accessToken)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil, err
|
||||||
|
}
|
||||||
|
if token == nil {
|
||||||
|
return false, nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
token.ExpiresIn = 0
|
||||||
|
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(token)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
application, err := getApplication(token.Owner, token.Application)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0, application, token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string, lang string) (string, *Application, error) {
|
||||||
|
if responseType != "code" && responseType != "token" && responseType != "id_token" {
|
||||||
|
return fmt.Sprintf(i18n.Translate(lang, "token:Grant_type: %s is not supported in this application"), responseType), nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
application, err := GetApplicationByClientId(clientId)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if application == nil {
|
||||||
|
return i18n.Translate(lang, "token:Invalid client_id"), nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !application.IsRedirectUriValid(redirectUri) {
|
||||||
|
return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask application for /api/get-app-login
|
||||||
|
application.ClientSecret = ""
|
||||||
|
return "", application, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) (*Code, error) {
|
||||||
|
user, err := GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return &Code{
|
||||||
|
Message: fmt.Sprintf("general:The user: %s doesn't exist", userId),
|
||||||
|
Code: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if user.IsForbidden {
|
||||||
|
return &Code{
|
||||||
|
Message: "error: the user is forbidden to sign in, please contact the administrator",
|
||||||
|
Code: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, application, err := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state, lang)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg != "" {
|
||||||
|
return &Code{
|
||||||
|
Message: msg,
|
||||||
|
Code: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ExtendUserWithRolesAndPermissions(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if challenge == "null" {
|
||||||
|
challenge = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: tokenName,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
Code: util.GenerateClientId(),
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
|
Scope: scope,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeChallenge: challenge,
|
||||||
|
CodeIsUsed: false,
|
||||||
|
CodeExpireIn: time.Now().Add(time.Minute * 5).Unix(),
|
||||||
|
}
|
||||||
|
_, err = AddToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Code{
|
||||||
|
Message: "",
|
||||||
|
Code: token.Code,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string) (interface{}, error) {
|
||||||
|
application, err := GetApplicationByClientId(clientId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if application == nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidClient,
|
||||||
|
ErrorDescription: "client_id is invalid",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if grantType is allowed in the current application
|
||||||
|
|
||||||
|
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
|
||||||
|
return &TokenError{
|
||||||
|
Error: UnsupportedGrantType,
|
||||||
|
ErrorDescription: fmt.Sprintf("grant_type: %s is not supported in this application", grantType),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var token *Token
|
||||||
|
var tokenError *TokenError
|
||||||
|
switch grantType {
|
||||||
|
case "authorization_code": // Authorization Code Grant
|
||||||
|
token, tokenError, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
|
||||||
|
case "password": // Resource Owner Password Credentials Grant
|
||||||
|
token, tokenError, err = GetPasswordToken(application, username, password, scope, host)
|
||||||
|
case "client_credentials": // Client Credentials Grant
|
||||||
|
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||||
|
case "refresh_token":
|
||||||
|
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return refreshToken2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag == "wechat_miniprogram" {
|
||||||
|
// Wechat Mini Program
|
||||||
|
token, tokenError, err = GetWechatMiniProgramToken(application, code, host, username, avatar, lang)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenError != nil {
|
||||||
|
return tokenError, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
token.CodeIsUsed = true
|
||||||
|
|
||||||
|
go updateUsedByCode(token)
|
||||||
|
|
||||||
|
tokenWrapper := &TokenWrapper{
|
||||||
|
AccessToken: token.AccessToken,
|
||||||
|
IdToken: token.AccessToken,
|
||||||
|
RefreshToken: token.RefreshToken,
|
||||||
|
TokenType: token.TokenType,
|
||||||
|
ExpiresIn: token.ExpiresIn,
|
||||||
|
Scope: token.Scope,
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenWrapper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) (interface{}, error) {
|
||||||
|
// check parameters
|
||||||
|
if grantType != "refresh_token" {
|
||||||
|
return &TokenError{
|
||||||
|
Error: UnsupportedGrantType,
|
||||||
|
ErrorDescription: "grant_type should be refresh_token",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
application, err := GetApplicationByClientId(clientId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if application == nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidClient,
|
||||||
|
ErrorDescription: "client_id is invalid",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidClient,
|
||||||
|
ErrorDescription: "client_secret is invalid",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check whether the refresh token is valid, and has not expired.
|
||||||
|
token, err := GetTokenByRefreshToken(refreshToken)
|
||||||
|
if err != nil || token == nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "refresh token is invalid, expired or revoked",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := getCertByApplication(application)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("cert: %s cannot be found", application.Cert),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ParseJwtToken(refreshToken, cert)
|
||||||
|
if err != nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a new token
|
||||||
|
user, err := getUser(application.Organization, token.User)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.IsForbidden {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ExtendUserWithRolesAndPermissions(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
||||||
|
if err != nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: EndpointError,
|
||||||
|
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newToken := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: tokenName,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
Code: util.GenerateClientId(),
|
||||||
|
AccessToken: newAccessToken,
|
||||||
|
RefreshToken: newRefreshToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
|
Scope: scope,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
}
|
||||||
|
_, err = AddToken(newToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = DeleteToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenWrapper := &TokenWrapper{
|
||||||
|
AccessToken: newToken.AccessToken,
|
||||||
|
IdToken: newToken.AccessToken,
|
||||||
|
RefreshToken: newToken.RefreshToken,
|
||||||
|
TokenType: newToken.TokenType,
|
||||||
|
ExpiresIn: newToken.ExpiresIn,
|
||||||
|
Scope: newToken.Scope,
|
||||||
|
}
|
||||||
|
return tokenWrapper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PkceChallenge: base64-URL-encoded SHA256 hash of verifier, per rfc 7636
|
||||||
|
func pkceChallenge(verifier string) string {
|
||||||
|
sum := sha256.Sum256([]byte(verifier))
|
||||||
|
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
|
||||||
|
return challenge
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGrantTypeValid
|
||||||
|
// Check if grantType is allowed in the current application
|
||||||
|
// authorization_code is allowed by default
|
||||||
|
func IsGrantTypeValid(method string, grantTypes []string) bool {
|
||||||
|
if method == "authorization_code" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, m := range grantTypes {
|
||||||
|
if m == method {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthorizationCodeToken
|
||||||
|
// Authorization code flow
|
||||||
|
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, *TokenError, error) {
|
||||||
|
if code == "" {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidRequest,
|
||||||
|
ErrorDescription: "authorization code should not be empty",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := getTokenByCode(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if token == nil {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "authorization code is invalid",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if token.CodeIsUsed {
|
||||||
|
// anti replay attacks
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "authorization code has been used",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "verifier is invalid",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if application.ClientSecret != clientSecret {
|
||||||
|
// when using PKCE, the Client Secret can be empty,
|
||||||
|
// but if it is provided, it must be accurate.
|
||||||
|
if token.CodeChallenge == "" {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidClient,
|
||||||
|
ErrorDescription: "client_secret is invalid",
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
if clientSecret != "" {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidClient,
|
||||||
|
ErrorDescription: "client_secret is invalid",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if application.Name != token.Application {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "the token is for wrong application (client_id)",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().Unix() > token.CodeExpireIn {
|
||||||
|
// code must be used within 5 minutes
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "authorization code has expired",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return token, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPasswordToken
|
||||||
|
// Resource Owner Password Credentials flow
|
||||||
|
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError, error) {
|
||||||
|
user, err := GetUserByFields(application.Organization, username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "the user does not exist",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Ldap != "" {
|
||||||
|
err = checkLdapUserPassword(user, password, "en")
|
||||||
|
} else {
|
||||||
|
err = CheckPassword(user, password, "en")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("invalid username or password: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.IsForbidden {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ExtendUserWithRolesAndPermissions(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: EndpointError,
|
||||||
|
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: tokenName,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
Code: util.GenerateClientId(),
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
|
Scope: scope,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeIsUsed: true,
|
||||||
|
}
|
||||||
|
_, err = AddToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClientCredentialsToken
|
||||||
|
// Client Credentials flow
|
||||||
|
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, *TokenError, error) {
|
||||||
|
if application.ClientSecret != clientSecret {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidClient,
|
||||||
|
ErrorDescription: "client_secret is invalid",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
nullUser := &User{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Id: application.GetId(),
|
||||||
|
Name: application.Name,
|
||||||
|
Type: "application",
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", scope, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: EndpointError,
|
||||||
|
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: tokenName,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: application.Organization,
|
||||||
|
User: nullUser.Name,
|
||||||
|
Code: util.GenerateClientId(),
|
||||||
|
AccessToken: accessToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
|
Scope: scope,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeIsUsed: true,
|
||||||
|
}
|
||||||
|
_, err = AddToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTokenByUser
|
||||||
|
// Implicit flow
|
||||||
|
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
|
||||||
|
err := ExtendUserWithRolesAndPermissions(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: tokenName,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
Code: util.GenerateClientId(),
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
|
Scope: scope,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeIsUsed: true,
|
||||||
|
}
|
||||||
|
_, err = AddToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWechatMiniProgramToken
|
||||||
|
// Wechat Mini Program flow
|
||||||
|
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string, lang string) (*Token, *TokenError, error) {
|
||||||
|
mpProvider := GetWechatMiniProgramProvider(application)
|
||||||
|
if mpProvider == nil {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidClient,
|
||||||
|
ErrorDescription: "the application does not support wechat mini program",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
provider, err := GetProvider(util.GetId("admin", mpProvider.Name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
|
||||||
|
session, err := mpIdp.GetSessionByCode(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("get wechat mini program session error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
openId, unionId := session.Openid, session.Unionid
|
||||||
|
if openId == "" && unionId == "" {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidRequest,
|
||||||
|
ErrorDescription: "the wechat mini program session is invalid",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
user, err := getUserByWechatId(application.Organization, openId, unionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
if !application.EnableSignUp {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: "the application does not allow to sign up new account",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// Add new user
|
||||||
|
var name string
|
||||||
|
if CheckUsername(username, lang) == "" {
|
||||||
|
name = username
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("wechat-%s", openId)
|
||||||
|
}
|
||||||
|
|
||||||
|
user = &User{
|
||||||
|
Owner: application.Organization,
|
||||||
|
Id: util.GenerateId(),
|
||||||
|
Name: name,
|
||||||
|
Avatar: avatar,
|
||||||
|
SignupApplication: application.Name,
|
||||||
|
WeChat: openId,
|
||||||
|
Type: "normal-user",
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
IsAdmin: false,
|
||||||
|
IsForbidden: false,
|
||||||
|
IsDeleted: false,
|
||||||
|
Properties: map[string]string{
|
||||||
|
UserPropertiesWechatOpenId: openId,
|
||||||
|
UserPropertiesWechatUnionId: unionId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = AddUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ExtendUserWithRolesAndPermissions(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &TokenError{
|
||||||
|
Error: EndpointError,
|
||||||
|
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: tokenName,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
Code: session.SessionKey, // a trick, because miniprogram does not use the code, so use the code field to save the session_key
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * 60,
|
||||||
|
Scope: "",
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeIsUsed: true,
|
||||||
|
}
|
||||||
|
_, err = AddToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return token, nil, nil
|
||||||
|
}
|
@ -366,7 +366,7 @@ class App extends Component {
|
|||||||
<FloatButton.BackTop />
|
<FloatButton.BackTop />
|
||||||
<CustomGithubCorner />
|
<CustomGithubCorner />
|
||||||
{
|
{
|
||||||
<Suspense fallback={<div>loading</div>}>
|
<Suspense fallback={null}>
|
||||||
<Layout id="parent-area">
|
<Layout id="parent-area">
|
||||||
<ManagementPage
|
<ManagementPage
|
||||||
account={this.state.account}
|
account={this.state.account}
|
||||||
|
@ -124,7 +124,7 @@ class ResourceListPage extends BaseListPage {
|
|||||||
...this.getColumnSearchProps("application"),
|
...this.getColumnSearchProps("application"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/applications/${record.organization}/${text}`}>
|
<Link to={`/applications/${record.owner}/${text}`}>
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -251,22 +251,8 @@ class UserEditPage extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderAccountItem(accountItem) {
|
renderAccountItem(accountItem) {
|
||||||
if (!accountItem.visible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAdmin = Setting.isLocalAdminUser(this.props.account);
|
const isAdmin = Setting.isLocalAdminUser(this.props.account);
|
||||||
|
|
||||||
if (accountItem.viewRule === "Self") {
|
|
||||||
if (!this.isSelfOrAdmin()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else if (accountItem.viewRule === "Admin") {
|
|
||||||
if (!isAdmin) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let disabled = false;
|
let disabled = false;
|
||||||
if (accountItem.modifyRule === "Self") {
|
if (accountItem.modifyRule === "Self") {
|
||||||
if (!this.isSelfOrAdmin()) {
|
if (!this.isSelfOrAdmin()) {
|
||||||
@ -1023,6 +1009,21 @@ class UserEditPage extends React.Component {
|
|||||||
<Form>
|
<Form>
|
||||||
{
|
{
|
||||||
this.getUserOrganization()?.accountItems?.map(accountItem => {
|
this.getUserOrganization()?.accountItems?.map(accountItem => {
|
||||||
|
if (!accountItem.visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdmin = Setting.isLocalAdminUser(this.props.account);
|
||||||
|
|
||||||
|
if (accountItem.viewRule === "Self") {
|
||||||
|
if (!this.isSelfOrAdmin()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (accountItem.viewRule === "Admin") {
|
||||||
|
if (!isAdmin) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={accountItem.name}>
|
<React.Fragment key={accountItem.name}>
|
||||||
<Form.Item name={accountItem.name}
|
<Form.Item name={accountItem.name}
|
||||||
|
Reference in New Issue
Block a user