Compare commits

..

3 Commits

Author SHA1 Message Date
Товарищ программист
7236cca8cf feat: implement CAS 3.0 (#659) 2022-04-11 21:11:31 +08:00
Yi Zhan
15daf5dbfe feat: add casdoor as saml idp support (#571)
* feat: add casdoor as saml idp support

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: merge code

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: modify response value

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: modify samlResponse generation method

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: generating a response using etree

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: change metadata url

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: modify front-end adaptation

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: recovering an incorrect override

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: change the samlResponse location

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: add relayState support

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-08 23:06:48 +08:00
Yi Zhan
0b546bba5e fix: grantTypes undefined err (#654)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-08 21:54:48 +08:00
20 changed files with 643 additions and 30 deletions

View File

@@ -101,6 +101,7 @@ p, *, *, GET, /.well-known/openid-configuration, *, *
p, *, *, *, /.well-known/jwks, *, *
p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /cas, *, *
`

View File

@@ -29,6 +29,7 @@ const (
ResponseTypeCode = "code"
ResponseTypeToken = "token"
ResponseTypeIdToken = "id_token"
ResponseTypeSaml = "saml"
ResponseTypeCas = "cas"
)
@@ -61,6 +62,7 @@ type RequestForm struct {
AutoSignin bool `json:"autoSignin"`
RelayState string `json:"relayState"`
SamlRequest string `json:"samlRequest"`
SamlResponse string `json:"samlResponse"`
}

View File

@@ -83,6 +83,13 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp = tokenToResponse(token)
}
} else if form.Type == ResponseTypeSaml { // saml flow
res, redirectUrl, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: redirectUrl}
} else if form.Type == ResponseTypeCas {
//not oauth but CAS SSO protocol
service := c.Input().Get("service")

View File

@@ -15,6 +15,7 @@
package controllers
import (
"encoding/xml"
"fmt"
"net/http"
"net/url"
@@ -42,7 +43,7 @@ func (c *RootController) CasValidate() {
c.Ctx.Output.Body([]byte("no\n"))
return
}
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
if ok, response, issuedService, _ := object.GetCasTokenByTicket(ticket); ok {
//check whether service is the one for which we previously issued token
if issuedService == service {
c.Ctx.Output.Body([]byte(fmt.Sprintf("yes\n%s\n", response.User)))
@@ -54,7 +55,25 @@ func (c *RootController) CasValidate() {
c.Ctx.Output.Body([]byte("no\n"))
}
func (c *RootController) CasServiceAndProxyValidate() {
func (c *RootController) CasServiceValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
if !strings.HasPrefix(ticket, "ST") {
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
}
c.CasP3ServiceAndProxyValidate()
}
func (c *RootController) CasProxyValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
if !strings.HasPrefix(ticket, "PT") {
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
}
c.CasP3ServiceAndProxyValidate()
}
func (c *RootController) CasP3ServiceAndProxyValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
service := c.Input().Get("service")
@@ -69,10 +88,9 @@ func (c *RootController) CasServiceAndProxyValidate() {
c.sendCasAuthenticationResponseErr(InvalidRequest, "service and ticket must exist", format)
return
}
ok, response, issuedService, userId := object.GetCasTokenByTicket(ticket)
//find the token
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
if ok {
//check whether service is the one for which we previously issued token
if strings.HasPrefix(service, issuedService) {
serviceResponse.Success = response
@@ -89,7 +107,7 @@ func (c *RootController) CasServiceAndProxyValidate() {
if pgtUrl != "" && serviceResponse.Failure == nil {
//that means we are in proxy web flow
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service)
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service, userId)
pgtiou := serviceResponse.Success.ProxyGrantingTicket
//todo: check whether it is https
pgtUrlObj, err := url.Parse(pgtUrl)
@@ -139,7 +157,7 @@ func (c *RootController) CasProxy() {
return
}
ok, authenticationSuccess, issuedService := object.GetCasTokenByPgt(pgt)
ok, authenticationSuccess, issuedService, userId := object.GetCasTokenByPgt(pgt)
if !ok {
c.sendCasProxyResponseErr(UnauthorizedService, "service not authorized", format)
return
@@ -150,7 +168,7 @@ func (c *RootController) CasProxy() {
newAuthenticationSuccess.Proxies = &object.CasProxies{}
}
newAuthenticationSuccess.Proxies.Proxies = append(newAuthenticationSuccess.Proxies.Proxies, issuedService)
proxyTicket := object.StoreCasTokenForProxyTicket(&newAuthenticationSuccess, targetService)
proxyTicket := object.StoreCasTokenForProxyTicket(&newAuthenticationSuccess, targetService, userId)
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
@@ -168,6 +186,55 @@ func (c *RootController) CasProxy() {
}
}
func (c *RootController) SamlValidate() {
c.Ctx.Output.Header("Content-Type", "text/xml; charset=utf-8")
target := c.Input().Get("TARGET")
body := c.Ctx.Input.RequestBody
envelopRequest := struct {
XMLName xml.Name `xml:"Envelope"`
Body struct {
XMLName xml.Name `xml:"Body"`
Content string `xml:",innerxml"`
}
}{}
err := xml.Unmarshal(body, &envelopRequest)
if err != nil {
c.ResponseError(err.Error())
return
}
response, service, err := object.GetValidationBySaml(envelopRequest.Body.Content, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
}
if !strings.HasPrefix(target, service) {
c.ResponseError(fmt.Sprintf("service %s and %s do not match", target, service))
return
}
envelopReponse := struct {
XMLName xml.Name `xml:"SOAP-ENV:Envelope"`
Xmlns string `xml:"xmlns:SOAP-ENV"`
Body struct {
XMLName xml.Name `xml:"SOAP-ENV:Body"`
Content string `xml:",innerxml"`
}
}{}
envelopReponse.Xmlns = "http://schemas.xmlsoap.org/soap/envelope/"
envelopReponse.Body.Content = response
data, err := xml.Marshal(envelopReponse)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Ctx.Output.Body([]byte(data))
}
func (c *RootController) sendCasProxyResponseErr(code, msg, format string) {
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",

33
controllers/saml.go Normal file
View File

@@ -0,0 +1,33 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"fmt"
"github.com/casdoor/casdoor/object"
)
func (c *ApiController) GetSamlMeta() {
host := c.Ctx.Request.Host
paramApp := c.Input().Get("application")
application := object.GetApplication(paramApp)
if application == nil {
c.ResponseError(fmt.Sprintf("err: application %s not found", paramApp))
}
metadata, _ := object.GetSamlMeta(application, host)
c.Data["xml"] = metadata
c.ServeXML()
}

5
go.mod
View File

@@ -3,10 +3,12 @@ module github.com/casdoor/casdoor
go 1.16
require (
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
github.com/astaxie/beego v1.12.3
github.com/aws/aws-sdk-go v1.37.30
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.5.1
github.com/casdoor/go-sms-sender v0.2.0
@@ -19,12 +21,15 @@ require (
github.com/golang-jwt/jwt/v4 v4.2.0
github.com/google/uuid v1.2.0
github.com/jinzhu/configor v1.2.1 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v0.9.0
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.6.0
github.com/russellhaering/goxmldsig v1.1.1
github.com/satori/go.uuid v1.2.0
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.7.0
github.com/tealeg/xlsx v1.0.5

8
go.sum
View File

@@ -44,6 +44,8 @@ github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8L
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b h1:EgJ6N2S0h1WfFIjU5/VVHWbMSVYXAluop97Qxpr/lfQ=
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b/go.mod h1:3SAoF0F5EbcOuBD5WT9nYkbIJieBS84cUQXADbXeBsU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -235,6 +237,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -254,6 +258,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/ma314smith/signedxml v0.0.0-20210628192057-abc5b481ae1c h1:UPJygtyk491bJJ/DnRJFuzcq9Dl9NSeFrJ7VdiRzMxc=
github.com/ma314smith/signedxml v0.0.0-20210628192057-abc5b481ae1c/go.mod h1:KEgVcb43+f5KFUH/x6Vd3NROG0AIL2CuKMrIqYsmx6E=
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
@@ -274,6 +280,8 @@ github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=

View File

@@ -300,7 +300,6 @@ func (application *Application) GetId() string {
func CheckRedirectUriValid(application *Application, redirectUri string) bool {
var validUri = false
for _, tmpUri := range application.RedirectUris {
fmt.Println(tmpUri, redirectUri)
if strings.Contains(redirectUri, tmpUri) {
validUri = true
break

352
object/saml_idp.go Normal file
View File

@@ -0,0 +1,352 @@
// 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 (
"bytes"
"compress/flate"
"crypto"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"encoding/pem"
"encoding/xml"
"fmt"
"io"
"time"
"github.com/RobotsAndPencils/go-saml"
"github.com/astaxie/beego"
"github.com/beevik/etree"
"github.com/golang-jwt/jwt/v4"
dsig "github.com/russellhaering/goxmldsig"
uuid "github.com/satori/go.uuid"
)
//returns a saml2 response
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, redirectUri []string) (*etree.Element, error) {
samlResponse := &etree.Element{
Space: "samlp",
Tag: "Response",
}
now := time.Now().UTC().Format(time.RFC3339)
expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339)
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
samlResponse.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
arId := uuid.NewV4()
samlResponse.CreateAttr("ID", fmt.Sprintf("_%s", arId))
samlResponse.CreateAttr("Version", "2.0")
samlResponse.CreateAttr("IssueInstant", now)
samlResponse.CreateAttr("Destination", destination)
samlResponse.CreateAttr("InResponseTo", fmt.Sprintf("Casdoor_%s", arId))
samlResponse.CreateElement("saml:Issuer").SetText(host)
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
assertion := samlResponse.CreateElement("saml:Assertion")
assertion.CreateAttr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
assertion.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema")
assertion.CreateAttr("ID", fmt.Sprintf("_%s", uuid.NewV4()))
assertion.CreateAttr("Version", "2.0")
assertion.CreateAttr("IssueInstant", now)
assertion.CreateElement("saml:Issuer").SetText(host)
subject := assertion.CreateElement("saml:Subject")
subject.CreateElement("saml:NameID").SetText(user.Email)
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
subjectConfirmationData.CreateAttr("InResponseTo", fmt.Sprintf("_%s", arId))
subjectConfirmationData.CreateAttr("Recipient", destination)
subjectConfirmationData.CreateAttr("NotOnOrAfter", expireTime)
condition := assertion.CreateElement("saml:Conditions")
condition.CreateAttr("NotBefore", now)
condition.CreateAttr("NotOnOrAfter", expireTime)
audience := condition.CreateElement("saml:AudienceRestriction")
audience.CreateElement("saml:Audience").SetText(iss)
for _, value := range redirectUri {
audience.CreateElement("saml:Audience").SetText(value)
}
authnStatement := assertion.CreateElement("saml:AuthnStatement")
authnStatement.CreateAttr("AuthnInstant", now)
authnStatement.CreateAttr("SessionIndex", fmt.Sprintf("_%s", uuid.NewV4()))
authnStatement.CreateAttr("SessionNotOnOrAfter", expireTime)
authnStatement.CreateElement("saml:AuthnContext").CreateElement("saml:AuthnContextClassRef").SetText("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")
attributes := assertion.CreateElement("saml:AttributeStatement")
email := attributes.CreateElement("saml:Attribute")
email.CreateAttr("Name", "Email")
email.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
email.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Email)
name := attributes.CreateElement("saml:Attribute")
name.CreateAttr("Name", "Name")
name.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
name.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Name)
displayName := attributes.CreateElement("saml:Attribute")
displayName.CreateAttr("Name", "DisplayName")
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
return samlResponse, nil
}
type X509Key struct {
X509Certificate string
PrivateKey string
}
func (x X509Key) GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err error) {
cert, _ = base64.StdEncoding.DecodeString(x.X509Certificate)
privateKey, err = jwt.ParseRSAPrivateKeyFromPEM([]byte(x.PrivateKey))
return privateKey, cert, err
}
//SAML METADATA
type IdpEntityDescriptor struct {
XMLName xml.Name `xml:"EntityDescriptor"`
DS string `xml:"xmlns:ds,attr"`
XMLNS string `xml:"xmlns,attr"`
MD string `xml:"xmlns:md,attr"`
EntityId string `xml:"entityID,attr"`
IdpSSODescriptor IdpSSODescriptor `xml:"IDPSSODescriptor"`
}
type KeyInfo struct {
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
X509Data X509Data `xml:",innerxml"`
}
type X509Data struct {
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"`
X509Certificate X509Certificate `xml:",innerxml"`
}
type X509Certificate struct {
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Certificate"`
Cert string `xml:",innerxml"`
}
type KeyDescriptor struct {
XMLName xml.Name `xml:"KeyDescriptor"`
Use string `xml:"use,attr"`
KeyInfo KeyInfo `xml:"KeyInfo"`
}
type IdpSSODescriptor struct {
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
SigningKeyDescriptor KeyDescriptor
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
SingleSignOnService SingleSignOnService `xml:"SingleSignOnService"`
Attribute []Attribute `xml:"Attribute"`
}
type NameIDFormat struct {
XMLName xml.Name
Value string `xml:",innerxml"`
}
type SingleSignOnService struct {
XMLName xml.Name
Binding string `xml:"Binding,attr"`
Location string `xml:"Location,attr"`
}
type Attribute struct {
XMLName xml.Name
Name string `xml:"Name,attr"`
NameFormat string `xml:"NameFormat,attr"`
FriendlyName string `xml:"FriendlyName,attr"`
Xmlns string `xml:"xmlns,attr"`
}
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
//_, originBackend := getOriginFromHost(host)
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
origin := beego.AppConfig.String("origin")
originFrontend, originBackend := getOriginFromHost(host)
if origin != "" {
originBackend = origin
}
d := IdpEntityDescriptor{
XMLName: xml.Name{
Local: "md:EntityDescriptor",
},
DS: "http://www.w3.org/2000/09/xmldsig#",
XMLNS: "urn:oasis:names:tc:SAML:2.0:metadata",
MD: "urn:oasis:names:tc:SAML:2.0:metadata",
EntityId: originBackend,
IdpSSODescriptor: IdpSSODescriptor{
SigningKeyDescriptor: KeyDescriptor{
Use: "signing",
KeyInfo: KeyInfo{
X509Data: X509Data{
X509Certificate: X509Certificate{
Cert: publicKey,
},
},
},
},
NameIDFormats: []NameIDFormat{
{Value: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"},
{Value: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"},
{Value: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"},
},
Attribute: []Attribute{
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Email", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "E-Mail"},
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "DisplayName", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "displayName"},
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Name", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "Name"},
},
SingleSignOnService: SingleSignOnService{
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
Location: fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name),
},
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
},
}
return &d, nil
}
//GenerateSamlResponse generates a SAML2.0 response
//parameter samlRequest is saml request in base64 format
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, error) {
//decode samlRequest
defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
var buffer bytes.Buffer
rdr := flate.NewReader(bytes.NewReader(defated))
io.Copy(&buffer, rdr)
var authnRequest saml.AuthnRequest
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
//verify samlRequest
if valid := CheckRedirectUriValid(application, authnRequest.Issuer.Url); !valid {
return "", "", fmt.Errorf("err: invalid issuer url")
}
//get publickey string
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
_, originBackend := getOriginFromHost(host)
//build signedResponse
samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, application.RedirectUris)
randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey,
X509Certificate: publicKey,
}
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())
}
res := base64.StdEncoding.EncodeToString([]byte(xmlStr))
return res, authnRequest.AssertionConsumerServiceURL, nil
}
//return a saml1.1 response(not 2.0)
func NewSamlResponse11(user *User, requestID string, host string) *etree.Element {
samlResponse := &etree.Element{
Space: "samlp",
Tag: "Response",
}
//create samlresponse
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:1.0:protocol")
samlResponse.CreateAttr("MajorVersion", "1")
samlResponse.CreateAttr("MinorVersion", "1")
responseID := uuid.NewV4()
samlResponse.CreateAttr("ResponseID", fmt.Sprintf("_%s", responseID))
samlResponse.CreateAttr("InResponseTo", requestID)
now := time.Now().UTC().Format(time.RFC3339)
expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339)
samlResponse.CreateAttr("IssueInstant", now)
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "samlp:Success")
//create assertion which is inside the response
assertion := samlResponse.CreateElement("saml:Assertion")
assertion.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:1.0:assertion")
assertion.CreateAttr("MajorVersion", "1")
assertion.CreateAttr("MinorVersion", "1")
assertion.CreateAttr("AssertionID", uuid.NewV4().String())
assertion.CreateAttr("Issuer", host)
assertion.CreateAttr("IssueInstant", now)
condition := assertion.CreateElement("saml:Conditions")
condition.CreateAttr("NotBefore", now)
condition.CreateAttr("NotOnOrAfter", expireTime)
//AuthenticationStatement inside assertion
authenticationStatement := assertion.CreateElement("saml:AuthenticationStatement")
authenticationStatement.CreateAttr("AuthenticationMethod", "urn:oasis:names:tc:SAML:1.0:am:password")
authenticationStatement.CreateAttr("AuthenticationInstant", now)
//subject inside AuthenticationStatement
subject := assertion.CreateElement("saml:Subject")
//nameIdentifier inside subject
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
//nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
nameIdentifier.SetText(user.Name)
//subjectConfirmation inside subject
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
subjectConfirmation.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
nameIdentifierInAttribute.SetText(user.Name)
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
data, _ := json.Marshal(user)
tmp := map[string]string{}
json.Unmarshal(data, &tmp)
for k, v := range tmp {
if v != "" {
attr := attributeStatement.CreateElement("saml:Attribute")
attr.CreateAttr("saml:AttributeName", k)
attr.CreateAttr("saml:AttributeNamespace", "http://www.ja-sig.org/products/cas/")
attr.CreateElement("saml:AttributeValue").SetText(v)
}
}
return samlResponse
}

View File

@@ -15,14 +15,19 @@
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/util"
dsig "github.com/russellhaering/goxmldsig"
)
type CasServiceResponse struct {
@@ -84,6 +89,7 @@ type CasAnyAttribute struct {
type CasAuthenticationSuccessWrapper struct {
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
Service string //to which service this token is issued
UserId string
}
type CasProxySuccess struct {
@@ -96,17 +102,32 @@ type CasProxyFailure struct {
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 StoreCasTokenForPgt(token *CasAuthenticationSuccess, service string) string {
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
}
@@ -115,33 +136,45 @@ func GenerateId() {
panic("unimplemented")
}
func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string) {
/**
@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
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
}
return false, nil, ""
return false, nil, "", ""
}
func GetCasTokenByTicket(ticket string) (bool, *CasAuthenticationSuccess, string) {
/**
@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
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
}
return false, nil, ""
return false, nil, "", ""
}
func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService string) string {
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) {
if user := GetUser(userId); user != nil {
authenticationSuccess := CasAuthenticationSuccess{
User: user.Name,
@@ -166,11 +199,69 @@ func GenerateCasToken(userId string, service string) (string, error) {
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: &authenticationSuccess,
Service: service,
UserId: userId,
})
return st, nil
} else {
return "", fmt.Errorf("invalid user Id")
}
}
/**
@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 := GetUser(userId)
if user == nil {
return "", "", fmt.Errorf("user %s found", userId)
}
application := GetApplicationByUser(user)
if application == nil {
return "", "", fmt.Errorf("application for user %s found", userId)
}
samlResponse := NewSamlResponse11(user, request.RequestID, host)
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey,
X509Certificate: publicKey,
}
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
}

View File

@@ -101,7 +101,7 @@ func willLog(subOwner string, subName string, method string, urlPath string, obj
}
func getUrlPath(urlPath string) string {
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate")) {
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
return "/cas"
}
return urlPath

View File

@@ -54,6 +54,7 @@ func initAPI() {
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
beego.Router("/api/saml/metadata", &controllers.ApiController{}, "GET:GetSamlMeta")
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
@@ -172,9 +173,13 @@ func initAPI() {
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy")
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
beego.Router("/cas/:organization/:application/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
}

View File

@@ -27,7 +27,7 @@ func StaticFilter(ctx *context.Context) {
if strings.HasPrefix(urlPath, "/api/") || strings.HasPrefix(urlPath, "/.well-known/") {
return
}
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate")) {
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
return
}

View File

@@ -656,6 +656,7 @@ class App extends Component {
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)}/>
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} />
<Route exact path="/callback" component={AuthCallback}/>

View File

@@ -61,7 +61,7 @@ class ApplicationEditPage extends React.Component {
getApplication() {
ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => {
if (application.grantTypes === null || application.grantTypes.length === 0) {
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) {
application.grantTypes = ["authorization_code"];
}
this.setState({

View File

@@ -135,7 +135,7 @@ class TokenEditPage extends React.Component {
</Col>
<Col span={22} >
<Input value={this.state.token.expiresIn} onChange={e => {
this.updateTokenField('expiresIn', e.target.value);
this.updateTokenField('expiresIn', parseInt(e.target.value));
}} />
</Col>
</Row>

View File

@@ -49,6 +49,10 @@ class AuthCallback extends React.Component {
const realRedirectUri = innerParams.get("redirect_uri");
// Casdoor's own login page, so "code" is not necessary
if (realRedirectUri === null) {
const samlRequest = innerParams.get("SAMLRequest");
if (samlRequest !== null && samlRequest !== undefined && samlRequest !== "") {
return "saml"
}
return "login";
}
@@ -92,6 +96,7 @@ class AuthCallback extends React.Component {
const applicationName = innerParams.get("application");
const providerName = innerParams.get("provider");
const method = innerParams.get("method");
const samlRequest = innerParams.get("SAMLRequest");
let redirectUri = `${window.location.origin}/callback`;
@@ -100,6 +105,7 @@ class AuthCallback extends React.Component {
application: applicationName,
provider: providerName,
code: code,
samlRequest: samlRequest,
// state: innerParams.get("state"),
state: applicationName,
redirectUri: redirectUri,
@@ -127,6 +133,10 @@ class AuthCallback extends React.Component {
} else if (responseType === "link") {
const from = innerParams.get("from");
Setting.goToLinkSoft(this, from);
} else if (responseType === "saml") {
const SAMLResponse = res.data;
const redirectUri = res.data2;
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
}
} else {
this.setState({

View File

@@ -53,6 +53,7 @@ class LoginPage extends React.Component {
classes: props,
type: props.type,
applicationName: props.applicationName !== undefined ? props.applicationName : (props.match === undefined ? null : props.match.params.applicationName),
owner : props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner),
application: null,
mode: props.mode !== undefined ? props.mode : (props.match === undefined ? null : props.match.params.mode), // "signup" or "signin"
isCodeSignin: false,
@@ -61,7 +62,6 @@ class LoginPage extends React.Component {
validEmailOrPhone: false,
validEmail: false,
validPhone: false,
owner: null,
};
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
this.state.owner = props.match?.params.owner
@@ -74,6 +74,8 @@ class LoginPage extends React.Component {
this.getApplication();
} else if (this.state.type === "code") {
this.getApplicationLogin();
} else if (this.state.type === "saml"){
this.getSamlApplication();
} else {
Util.showMessage("error", `Unknown authentication type: ${this.state.type}`);
}
@@ -110,6 +112,19 @@ class LoginPage extends React.Component {
});
}
getSamlApplication(){
if (this.state.applicationName === null){
return;
}
ApplicationBackend.getApplication(this.state.owner, this.state.applicationName)
.then((application) => {
this.setState({
application: application,
});
}
);
}
getApplicationObj() {
if (this.props.application !== undefined) {
return this.props.application;
@@ -157,7 +172,16 @@ class LoginPage extends React.Component {
values["type"] = this.state.type;
}
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
if (oAuthParams !== null){
values["samlRequest"] = oAuthParams.samlRequest;
}
if (values["samlRequest"] != null && values["samlRequest"] !== "") {
values["type"] = "saml";
}
AuthBackend.login(values, oAuthParams)
.then((res) => {
if (res.status === 'ok') {
@@ -198,6 +222,10 @@ class LoginPage extends React.Component {
} else if (responseType === "token" || responseType === "id_token") {
const accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
} else if (responseType === "saml") {
const SAMLResponse = res.data;
const redirectUri = res.data2;
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
}
} else {
Util.showMessage("error", `Failed to log in: ${res.msg}`);

View File

@@ -98,11 +98,13 @@ export function getOAuthGetParameters(params) {
const redirectUri = getRefinedValue(queries.get("redirect_uri"));
const scope = getRefinedValue(queries.get("scope"));
const state = getRefinedValue(queries.get("state"));
const nonce = getRefinedValue(queries.get("nonce"))
const challengeMethod = getRefinedValue(queries.get("code_challenge_method"))
const codeChallenge = getRefinedValue(queries.get("code_challenge"))
if (clientId === undefined || clientId === null || clientId === "") {
const nonce = getRefinedValue(queries.get("nonce"));
const challengeMethod = getRefinedValue(queries.get("code_challenge_method"));
const codeChallenge = getRefinedValue(queries.get("code_challenge"));
const samlRequest = getRefinedValue(queries.get("SAMLRequest"));
const relayState = getRefinedValue(queries.get("RelayState"));
if ((clientId === undefined || clientId === null || clientId === "") && (samlRequest === "" || samlRequest === undefined)) {
// login
return null;
} else {
@@ -116,6 +118,8 @@ export function getOAuthGetParameters(params) {
nonce: nonce,
challengeMethod: challengeMethod,
codeChallenge: codeChallenge,
samlRequest: samlRequest,
relayState: relayState,
};
}
}