feat: support CAS with organizations and applications (#621)

This commit is contained in:
Товарищ программист 2022-04-04 00:09:04 +08:00 committed by GitHub
parent 2023795f3c
commit 3cf1b990be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 665 additions and 54 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, *, *, *, /cas, *, *
`
sa := stringadapter.NewAdapter(ruleText)

View File

@ -29,6 +29,7 @@ const (
ResponseTypeCode = "code"
ResponseTypeToken = "token"
ResponseTypeIdToken = "id_token"
ResponseTypeCas = "cas"
)
type RequestForm struct {

View File

@ -83,8 +83,25 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp = tokenToResponse(token)
}
} else if form.Type == ResponseTypeCas {
//not oauth but CAS SSO protocol
service := c.Input().Get("service")
resp = wrapErrorResponse(nil)
if service != "" {
st, err := object.GenerateCasToken(userId, service)
if err != nil {
resp = wrapErrorResponse(err)
} else {
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
resp.Data = st
}
}
if application.EnableSigninSession || application.HasPromptPage() {
// The prompt page needs the user to be signed in
c.SetSessionUsername(userId)
}
} else {
resp = wrapErrorResponse(fmt.Errorf("Unknown response type: %s", form.Type))
}
// if user did not check auto signin

204
controllers/cas.go Normal file
View File

@ -0,0 +1,204 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/casdoor/casdoor/object"
)
const (
InvalidRequest string = "INVALID_REQUEST"
InvalidTicketSpec string = "INVALID_TICKET_SPEC"
UnauthorizedServiceProxy string = "UNAUTHORIZED_SERVICE_PROXY"
InvalidProxyCallback string = "INVALID_PROXY_CALLBACK"
InvalidTicket string = "INVALID_TICKET"
InvalidService string = "INVALID_SERVICE"
InteralError string = "INTERNAL_ERROR"
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
)
func (c *RootController) CasValidate() {
ticket := c.Input().Get("ticket")
service := c.Input().Get("service")
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
if service == "" || ticket == "" {
c.Ctx.Output.Body([]byte("no\n"))
return
}
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
//check whether service is the one for which we previously issued token
if issuedService == service {
c.Ctx.Output.Body([]byte(fmt.Sprintf("yes\n%s\n", response.User)))
return
}
}
//token not found
c.Ctx.Output.Body([]byte("no\n"))
}
func (c *RootController) CasServiceAndProxyValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
service := c.Input().Get("service")
pgtUrl := c.Input().Get("pgtUrl")
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
}
//check whether all required parameters are met
if service == "" || ticket == "" {
c.sendCasAuthenticationResponseErr(InvalidRequest, "service and ticket must exist", format)
return
}
//find the token
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
//check whether service is the one for which we previously issued token
if strings.HasPrefix(service, issuedService) {
serviceResponse.Success = response
} else {
//service not match
c.sendCasAuthenticationResponseErr(InvalidService, fmt.Sprintf("service %s and %s does not match", service, issuedService), format)
return
}
} else {
//token not found
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
return
}
if pgtUrl != "" && serviceResponse.Failure == nil {
//that means we are in proxy web flow
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service)
pgtiou := serviceResponse.Success.ProxyGrantingTicket
//todo: check whether it is https
pgtUrlObj, err := url.Parse(pgtUrl)
if pgtUrlObj.Scheme != "https" {
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
return
}
//make a request to pgturl passing pgt and pgtiou
if err != nil {
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
return
}
param := pgtUrlObj.Query()
param.Add("pgtId", pgt)
param.Add("pgtIou", pgtiou)
pgtUrlObj.RawQuery = param.Encode()
request, err := http.NewRequest("GET", pgtUrlObj.String(), nil)
if err != nil {
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
return
}
resp, err := http.DefaultClient.Do(request)
if err != nil || !(resp.StatusCode >= 200 && resp.StatusCode < 400) {
//failed to send request
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
return
}
}
// everything is ok, send the response
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) CasProxy() {
pgt := c.Input().Get("pgt")
targetService := c.Input().Get("targetService")
format := c.Input().Get("format")
if pgt == "" || targetService == "" {
c.sendCasProxyResponseErr(InvalidRequest, "pgt and targetService must exist", format)
return
}
ok, authenticationSuccess, issuedService := object.GetCasTokenByPgt(pgt)
if !ok {
c.sendCasProxyResponseErr(UnauthorizedService, "service not authorized", format)
return
}
newAuthenticationSuccess := authenticationSuccess.DeepCopy()
if newAuthenticationSuccess.Proxies == nil {
newAuthenticationSuccess.Proxies = &object.CasProxies{}
}
newAuthenticationSuccess.Proxies.Proxies = append(newAuthenticationSuccess.Proxies.Proxies, issuedService)
proxyTicket := object.StoreCasTokenForProxyTicket(&newAuthenticationSuccess, targetService)
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
ProxySuccess: &object.CasProxySuccess{
ProxyTicket: proxyTicket,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) sendCasProxyResponseErr(code, msg, format string) {
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
ProxyFailure: &object.CasProxyFailure{
Code: code,
Message: msg,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format string) {
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
Failure: &object.CasAuthenticationFailure{
Code: code,
Message: msg,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}

1
go.mod
View File

@ -25,7 +25,6 @@ require (
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 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.7.0
github.com/tealeg/xlsx v1.0.5

2
go.sum
View File

@ -79,8 +79,6 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

236
object/token_cas.go Normal file
View File

@ -0,0 +1,236 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"encoding/json"
"encoding/xml"
"fmt"
"math/rand"
"sync"
"time"
"github.com/casdoor/casdoor/util"
)
type CasServiceResponse struct {
XMLName xml.Name `xml:"cas:serviceResponse" json:"-"`
Xmlns string `xml:"xmlns:cas,attr"`
Failure *CasAuthenticationFailure
Success *CasAuthenticationSuccess
ProxySuccess *CasProxySuccess
ProxyFailure *CasProxyFailure
}
type CasAuthenticationFailure struct {
XMLName xml.Name `xml:"cas:authenticationFailure" json:"-"`
Code string `xml:"code,attr"`
Message string `xml:",innerxml"`
}
type CasAuthenticationSuccess struct {
XMLName xml.Name `xml:"cas:authenticationSuccess" json:"-"`
User string `xml:"cas:user"`
ProxyGrantingTicket string `xml:"cas:proxyGrantingTicket,omitempty"`
Proxies *CasProxies `xml:"cas:proxies"`
Attributes *CasAttributes `xml:"cas:attributes"`
ExtraAttributes []*CasAnyAttribute `xml:",any"`
}
type CasProxies struct {
XMLName xml.Name `xml:"cas:proxies" json:"-"`
Proxies []string `xml:"cas:proxy"`
}
type CasAttributes struct {
XMLName xml.Name `xml:"cas:attributes" json:"-"`
AuthenticationDate time.Time `xml:"cas:authenticationDate"`
LongTermAuthenticationRequestTokenUsed bool `xml:"cas:longTermAuthenticationRequestTokenUsed"`
IsFromNewLogin bool `xml:"cas:isFromNewLogin"`
MemberOf []string `xml:"cas:memberOf"`
UserAttributes *CasUserAttributes
ExtraAttributes []*CasAnyAttribute `xml:",any"`
}
type CasUserAttributes struct {
XMLName xml.Name `xml:"cas:userAttributes" json:"-"`
Attributes []*CasNamedAttribute `xml:"cas:attribute"`
AnyAttributes []*CasAnyAttribute `xml:",any"`
}
type CasNamedAttribute struct {
XMLName xml.Name `xml:"cas:attribute" json:"-"`
Name string `xml:"name,attr,omitempty"`
Value string `xml:",innerxml"`
}
type CasAnyAttribute struct {
XMLName xml.Name
Value string `xml:",chardata"`
}
type CasAuthenticationSuccessWrapper struct {
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
Service string //to which service this token is issued
}
type CasProxySuccess struct {
XMLName xml.Name `xml:"cas:proxySuccess" json:"-"`
ProxyTicket string `xml:"cas:proxyTicket"`
}
type CasProxyFailure struct {
XMLName xml.Name `xml:"cas:proxyFailure" json:"-"`
Code string `xml:"code,attr"`
Message string `xml:",innerxml"`
}
//st is short for service ticket
var stToServiceResponse sync.Map
//pgt is short for proxy granting ticket
var pgtToServiceResponse sync.Map
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service string) string {
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: token,
Service: service,
})
return pgt
}
func GenerateId() {
panic("unimplemented")
}
func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string) {
if responseWrapperType, ok := pgtToServiceResponse.LoadAndDelete(pgt); ok {
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service
}
return false, nil, ""
}
func GetCasTokenByTicket(ticket string) (bool, *CasAuthenticationSuccess, string) {
if responseWrapperType, ok := stToServiceResponse.LoadAndDelete(ticket); ok {
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service
}
return false, nil, ""
}
func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService string) string {
proxyTicket := fmt.Sprintf("PT-%s", util.GenerateId())
stToServiceResponse.Store(proxyTicket, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: token,
Service: targetService,
})
return proxyTicket
}
func GenerateCasToken(userId string, service string) (string, error) {
if user := GetUser(userId); user != nil {
authenticationSuccess := CasAuthenticationSuccess{
User: user.Name,
Attributes: &CasAttributes{
AuthenticationDate: time.Now(),
UserAttributes: &CasUserAttributes{},
},
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
}
data, _ := json.Marshal(user)
tmp := map[string]string{}
json.Unmarshal(data, &tmp)
for k, v := range tmp {
if v != "" {
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k,
Value: v,
})
}
}
st := fmt.Sprintf("ST-%d", rand.Int())
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: &authenticationSuccess,
Service: service,
})
return st, nil
} else {
return "", fmt.Errorf("invalid user Id")
}
}
func (c *CasAuthenticationSuccess) DeepCopy() CasAuthenticationSuccess {
res := *c
//copy proxy
if c.Proxies != nil {
tmp := c.Proxies.DeepCopy()
res.Proxies = &tmp
}
if c.Attributes != nil {
tmp := c.Attributes.DeepCopy()
res.Attributes = &tmp
}
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
for i, e := range c.ExtraAttributes {
tmp := *e
res.ExtraAttributes[i] = &tmp
}
return res
}
func (c *CasProxies) DeepCopy() CasProxies {
res := CasProxies{
Proxies: make([]string, len(c.Proxies)),
}
copy(res.Proxies, c.Proxies)
return res
}
func (c *CasAttributes) DeepCopy() CasAttributes {
res := *c
if c.MemberOf != nil {
res.MemberOf = make([]string, len(c.MemberOf))
copy(res.MemberOf, c.MemberOf)
}
tmp := c.UserAttributes.DeepCopy()
res.UserAttributes = &tmp
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
for i, e := range c.ExtraAttributes {
tmp := *e
res.ExtraAttributes[i] = &tmp
}
return res
}
func (c *CasUserAttributes) DeepCopy() CasUserAttributes {
res := CasUserAttributes{
AnyAttributes: make([]*CasAnyAttribute, len(c.AnyAttributes)),
Attributes: make([]*CasNamedAttribute, len(c.Attributes)),
}
for i, a := range c.AnyAttributes {
var tmp = *a
res.AnyAttributes[i] = &tmp
}
for i, a := range c.Attributes {
var tmp = *a
res.Attributes[i] = &tmp
}
return res
}

View File

@ -100,10 +100,17 @@ func willLog(subOwner string, subName string, method string, urlPath string, obj
return true
}
func getUrlPath(urlPath string) string {
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate")) {
return "/cas"
}
return urlPath
}
func AuthzFilter(ctx *context.Context) {
subOwner, subName := getSubject(ctx)
method := ctx.Request.Method
urlPath := ctx.Request.URL.Path
urlPath := getUrlPath(ctx.Request.URL.Path)
objOwner, objName := getObject(ctx)
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)

View File

@ -171,4 +171,10 @@ 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/proxy", &controllers.RootController{}, "GET:CasProxy")
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
}

View File

@ -27,6 +27,9 @@ 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")) {
return
}
path := "web/build"
if urlPath == "/" {

View File

@ -18,6 +18,22 @@ module.exports = {
'/.well-known/openid-configuration': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/serviceValidate': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/proxyValidate': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/proxy': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/validate': {
target: 'http://localhost:8000',
changeOrigin: true,
}
},
},

View File

@ -68,6 +68,7 @@ import i18next from 'i18next';
import PromptPage from "./auth/PromptPage";
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
import SamlCallback from './auth/SamlCallback';
import CasLogout from "./auth/CasLogout";
const { Header, Footer } = Layout;
@ -642,7 +643,8 @@ class App extends Component {
window.location.pathname.startsWith("/login") ||
window.location.pathname.startsWith("/callback") ||
window.location.pathname.startsWith("/prompt") ||
window.location.pathname.startsWith("/forget");
window.location.pathname.startsWith("/forget") ||
window.location.pathname.startsWith("/cas");
}
renderPage() {
@ -654,6 +656,8 @@ 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="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} />
<Route exact path="/callback" component={AuthCallback}/>
<Route exact path="/callback/saml" component={SamlCallback}/>
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/>

View File

@ -62,6 +62,14 @@ export function login(values, oAuthParams) {
}).then(res => res.json());
}
export function loginCas(values, params) {
return fetch(`${authConfig.serverUrl}/api/login?service=${params.service}`, {
method: 'POST',
credentials: "include",
body: JSON.stringify(values),
}).then(res => res.json());
}
export function logout() {
return fetch(`${authConfig.serverUrl}/api/logout`, {
method: 'POST',

68
web/src/auth/CasLogout.js Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Spin} from "antd";
import {withRouter} from "react-router-dom";
import * as AuthBackend from "./AuthBackend";
import * as Setting from "../Setting";
import i18next from "i18next";
class CasLogout extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
msg: null,
};
if (props.match?.params.casApplicationName !== undefined) {
this.state.owner = props.match?.params.owner
this.state.applicationName = props.match?.params.casApplicationName
}
}
UNSAFE_componentWillMount() {
const params = new URLSearchParams(this.props.location.search);
AuthBackend.logout()
.then((res) => {
if (res.status === 'ok') {
Setting.showMessage("success", `Logged out successfully`);
this.props.clearAccount()
let redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
} else if (params.has("service")) {
Setting.goToLink(params.get("service"))
} else {
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
}
} else {
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
}
});
}
render() {
return (
<div style={{textAlign: "center"}}>
{
<Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} />
}
</div>
)
}
}
export default withRouter(CasLogout);

View File

@ -61,11 +61,16 @@ 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
this.state.applicationName = props.match?.params.casApplicationName
}
}
UNSAFE_componentWillMount() {
if (this.state.type === "login") {
if (this.state.type === "login" || this.state.type === "cas") {
this.getApplication();
} else if (this.state.type === "code") {
this.getApplicationLogin();
@ -120,6 +125,31 @@ class LoginPage extends React.Component {
onFinish(values) {
const application = this.getApplicationObj();
const ths = this;
//here we are supposed to judge whether casdoor is working as a oauth server or CAS server
if (this.state.type === "cas") {
//cas
const casParams = Util.getCasParameters()
values["type"] = this.state.type;
AuthBackend.loginCas(values, casParams).then((res) => {
if (res.status === 'ok') {
let msg = "Logged in successfully. "
if (casParams.service == "") {
//If service was not specified, CAS MUST display a message notifying the client that it has successfully initiated a single sign-on session.
msg += "Now you can visit apps protected by casdoor."
}
Util.showMessage("success", msg);
if (casParams.service !== "") {
let st = res.data
window.location.href = casParams.service + "?ticket=" + st
}
} else {
Util.showMessage("error", `Failed to log in: ${res.msg}`);
}
})
} else {
//oauth
const oAuthParams = Util.getOAuthGetParameters();
if (oAuthParams !== null && oAuthParams.responseType != null && oAuthParams.responseType !== "") {
values["type"] = oAuthParams.responseType
@ -173,6 +203,7 @@ class LoginPage extends React.Component {
Util.showMessage("error", `Failed to log in: ${res.msg}`);
}
});
}
};
getSigninButton(type) {

View File

@ -79,6 +79,18 @@ function getRefinedValue(value){
return (value === null)? "" : value
}
export function getCasParameters(params){
const queries = (params !== undefined) ? params : new URLSearchParams(window.location.search);
const service = getRefinedValue(queries.get("service"))
const renew = getRefinedValue(queries.get("renew"))
const gateway = getRefinedValue(queries.get("gateway"))
return {
service: service,
renew: renew,
gateway: gateway,
}
}
export function getOAuthGetParameters(params) {
const queries = (params !== undefined) ? params : new URLSearchParams(window.location.search);
const clientId = getRefinedValue(queries.get("client_id"));