mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-22 18:25:47 +08:00
366 lines
10 KiB
Go
366 lines
10 KiB
Go
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package object
|
|
|
|
import (
|
|
"crypto"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"math/rand"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/beevik/etree"
|
|
"github.com/casdoor/casdoor/i18n"
|
|
"github.com/casdoor/casdoor/util"
|
|
dsig "github.com/russellhaering/goxmldsig"
|
|
)
|
|
|
|
type CasServiceResponse struct {
|
|
XMLName xml.Name `xml:"cas:serviceResponse" json:"-"`
|
|
Xmlns string `xml:"xmlns:cas,attr"`
|
|
Failure *CasAuthenticationFailure
|
|
Success *CasAuthenticationSuccess
|
|
ProxySuccess *CasProxySuccess
|
|
ProxyFailure *CasProxyFailure
|
|
}
|
|
|
|
type CasAuthenticationFailure struct {
|
|
XMLName xml.Name `xml:"cas:authenticationFailure" json:"-"`
|
|
Code string `xml:"code,attr"`
|
|
Message string `xml:",innerxml"`
|
|
}
|
|
|
|
type CasAuthenticationSuccess struct {
|
|
XMLName xml.Name `xml:"cas:authenticationSuccess" json:"-"`
|
|
User string `xml:"cas:user"`
|
|
ProxyGrantingTicket string `xml:"cas:proxyGrantingTicket,omitempty"`
|
|
Proxies *CasProxies `xml:"cas:proxies"`
|
|
Attributes *CasAttributes `xml:"cas:attributes"`
|
|
ExtraAttributes []*CasAnyAttribute `xml:",any"`
|
|
}
|
|
|
|
type CasProxies struct {
|
|
XMLName xml.Name `xml:"cas:proxies" json:"-"`
|
|
Proxies []string `xml:"cas:proxy"`
|
|
}
|
|
|
|
type CasAttributes struct {
|
|
XMLName xml.Name `xml:"cas:attributes" json:"-"`
|
|
AuthenticationDate time.Time `xml:"cas:authenticationDate"`
|
|
LongTermAuthenticationRequestTokenUsed bool `xml:"cas:longTermAuthenticationRequestTokenUsed"`
|
|
IsFromNewLogin bool `xml:"cas:isFromNewLogin"`
|
|
MemberOf []string `xml:"cas:memberOf"`
|
|
UserAttributes *CasUserAttributes
|
|
ExtraAttributes []*CasAnyAttribute `xml:",any"`
|
|
}
|
|
|
|
type CasUserAttributes struct {
|
|
XMLName xml.Name `xml:"cas:userAttributes" json:"-"`
|
|
Attributes []*CasNamedAttribute `xml:"cas:attribute"`
|
|
AnyAttributes []*CasAnyAttribute `xml:",any"`
|
|
}
|
|
|
|
type CasNamedAttribute struct {
|
|
XMLName xml.Name `xml:"cas:attribute" json:"-"`
|
|
Name string `xml:"name,attr,omitempty"`
|
|
Value string `xml:",innerxml"`
|
|
}
|
|
|
|
type CasAnyAttribute struct {
|
|
XMLName xml.Name
|
|
Value string `xml:",chardata"`
|
|
}
|
|
|
|
type CasAuthenticationSuccessWrapper struct {
|
|
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
|
|
Service string // to which service this token is issued
|
|
UserId string
|
|
}
|
|
|
|
type CasProxySuccess struct {
|
|
XMLName xml.Name `xml:"cas:proxySuccess" json:"-"`
|
|
ProxyTicket string `xml:"cas:proxyTicket"`
|
|
}
|
|
type CasProxyFailure struct {
|
|
XMLName xml.Name `xml:"cas:proxyFailure" json:"-"`
|
|
Code string `xml:"code,attr"`
|
|
Message string `xml:",innerxml"`
|
|
}
|
|
|
|
type Saml11Request struct {
|
|
XMLName xml.Name `xml:"Request"`
|
|
SAMLP string `xml:"samlp,attr"`
|
|
MajorVersion string `xml:"MajorVersion,attr"`
|
|
MinorVersion string `xml:"MinorVersion,attr"`
|
|
RequestID string `xml:"RequestID,attr"`
|
|
IssueInstant string `xml:"IssueInstance,attr"`
|
|
AssertionArtifact Saml11AssertionArtifact
|
|
}
|
|
type Saml11AssertionArtifact struct {
|
|
XMLName xml.Name `xml:"AssertionArtifact"`
|
|
InnerXML string `xml:",innerxml"`
|
|
}
|
|
|
|
// st is short for service ticket
|
|
var stToServiceResponse sync.Map
|
|
|
|
// pgt is short for proxy granting ticket
|
|
var pgtToServiceResponse sync.Map
|
|
|
|
func CheckCasLogin(application *Application, lang string, service string) error {
|
|
if len(application.RedirectUris) > 0 && !application.IsRedirectUriValid(service) {
|
|
return fmt.Errorf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), service)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service, userId string) string {
|
|
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
|
|
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{
|
|
AuthenticationSuccess: token,
|
|
Service: service,
|
|
UserId: userId,
|
|
})
|
|
return pgt
|
|
}
|
|
|
|
func GenerateId() {
|
|
panic("unimplemented")
|
|
}
|
|
|
|
// GetCasTokenByPgt
|
|
/**
|
|
@ret1: whether a token is found
|
|
@ret2: token, nil if not found
|
|
@ret3: the service URL who requested to issue this token
|
|
@ret4: userIf of user who requested to issue this token
|
|
*/
|
|
func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string, string) {
|
|
if responseWrapperType, ok := pgtToServiceResponse.LoadAndDelete(pgt); ok {
|
|
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
|
|
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
|
|
}
|
|
return false, nil, "", ""
|
|
}
|
|
|
|
// GetCasTokenByTicket
|
|
/**
|
|
@ret1: whether a token is found
|
|
@ret2: token, nil if not found
|
|
@ret3: the service URL who requested to issue this token
|
|
@ret4: userIf of user who requested to issue this token
|
|
*/
|
|
func GetCasTokenByTicket(ticket string) (bool, *CasAuthenticationSuccess, string, string) {
|
|
if responseWrapperType, ok := stToServiceResponse.LoadAndDelete(ticket); ok {
|
|
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
|
|
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
|
|
}
|
|
return false, nil, "", ""
|
|
}
|
|
|
|
func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService, userId string) string {
|
|
proxyTicket := fmt.Sprintf("PT-%s", util.GenerateId())
|
|
stToServiceResponse.Store(proxyTicket, &CasAuthenticationSuccessWrapper{
|
|
AuthenticationSuccess: token,
|
|
Service: targetService,
|
|
UserId: userId,
|
|
})
|
|
return proxyTicket
|
|
}
|
|
|
|
func GenerateCasToken(userId string, service string) (string, error) {
|
|
user, err := GetUser(userId)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if user == nil {
|
|
return "", fmt.Errorf("The user: %s doesn't exist", userId)
|
|
}
|
|
|
|
user, _ = GetMaskedUser(user, false)
|
|
|
|
authenticationSuccess := CasAuthenticationSuccess{
|
|
User: user.Name,
|
|
Attributes: &CasAttributes{
|
|
AuthenticationDate: time.Now(),
|
|
UserAttributes: &CasUserAttributes{},
|
|
},
|
|
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
|
|
}
|
|
|
|
data, err := json.Marshal(user)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
tmp := map[string]string{}
|
|
err = json.Unmarshal(data, &tmp)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for k, v := range tmp {
|
|
if v != "" {
|
|
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
|
Name: k,
|
|
Value: v,
|
|
})
|
|
}
|
|
}
|
|
|
|
st := fmt.Sprintf("ST-%d", rand.Int())
|
|
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
|
|
AuthenticationSuccess: &authenticationSuccess,
|
|
Service: service,
|
|
UserId: userId,
|
|
})
|
|
return st, nil
|
|
}
|
|
|
|
// GetValidationBySaml
|
|
/**
|
|
@ret1: saml response
|
|
@ret2: the service URL who requested to issue this token
|
|
@ret3: error
|
|
*/
|
|
func GetValidationBySaml(samlRequest string, host string) (string, string, error) {
|
|
var request Saml11Request
|
|
err := xml.Unmarshal([]byte(samlRequest), &request)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
ticket := request.AssertionArtifact.InnerXML
|
|
if ticket == "" {
|
|
return "", "", fmt.Errorf("samlp:AssertionArtifact field not found")
|
|
}
|
|
|
|
ok, _, service, userId := GetCasTokenByTicket(ticket)
|
|
if !ok {
|
|
return "", "", fmt.Errorf("ticket %s found", ticket)
|
|
}
|
|
|
|
user, err := GetUser(userId)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
if user == nil {
|
|
return "", "", fmt.Errorf("user %s found", userId)
|
|
}
|
|
|
|
application, err := GetApplicationByUser(user)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
if application == nil {
|
|
return "", "", fmt.Errorf("application for user %s found", userId)
|
|
}
|
|
|
|
samlResponse := NewSamlResponse11(user, request.RequestID, host)
|
|
|
|
cert, err := getCertByApplication(application)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
block, _ := pem.Decode([]byte(cert.Certificate))
|
|
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
|
randomKeyStore := &X509Key{
|
|
PrivateKey: cert.PrivateKey,
|
|
X509Certificate: certificate,
|
|
}
|
|
|
|
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
|
ctx.Hash = crypto.SHA1
|
|
signedXML, err := ctx.SignEnveloped(samlResponse)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
|
}
|
|
|
|
doc := etree.NewDocument()
|
|
doc.SetRoot(signedXML)
|
|
xmlStr, err := doc.WriteToString()
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("err: %s", err.Error())
|
|
}
|
|
return xmlStr, service, nil
|
|
}
|
|
|
|
func (c *CasAuthenticationSuccess) DeepCopy() CasAuthenticationSuccess {
|
|
res := *c
|
|
// copy proxy
|
|
if c.Proxies != nil {
|
|
tmp := c.Proxies.DeepCopy()
|
|
res.Proxies = &tmp
|
|
}
|
|
if c.Attributes != nil {
|
|
tmp := c.Attributes.DeepCopy()
|
|
res.Attributes = &tmp
|
|
}
|
|
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
|
|
for i, e := range c.ExtraAttributes {
|
|
tmp := *e
|
|
res.ExtraAttributes[i] = &tmp
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (c *CasProxies) DeepCopy() CasProxies {
|
|
res := CasProxies{
|
|
Proxies: make([]string, len(c.Proxies)),
|
|
}
|
|
copy(res.Proxies, c.Proxies)
|
|
return res
|
|
}
|
|
|
|
func (c *CasAttributes) DeepCopy() CasAttributes {
|
|
res := *c
|
|
if c.MemberOf != nil {
|
|
res.MemberOf = make([]string, len(c.MemberOf))
|
|
copy(res.MemberOf, c.MemberOf)
|
|
}
|
|
tmp := c.UserAttributes.DeepCopy()
|
|
res.UserAttributes = &tmp
|
|
|
|
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
|
|
for i, e := range c.ExtraAttributes {
|
|
tmp := *e
|
|
res.ExtraAttributes[i] = &tmp
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (c *CasUserAttributes) DeepCopy() CasUserAttributes {
|
|
res := CasUserAttributes{
|
|
AnyAttributes: make([]*CasAnyAttribute, len(c.AnyAttributes)),
|
|
Attributes: make([]*CasNamedAttribute, len(c.Attributes)),
|
|
}
|
|
for i, a := range c.AnyAttributes {
|
|
tmp := *a
|
|
res.AnyAttributes[i] = &tmp
|
|
}
|
|
for i, a := range c.Attributes {
|
|
tmp := *a
|
|
res.Attributes[i] = &tmp
|
|
}
|
|
return res
|
|
}
|