feat: support third-party application to login with SAML rather than only Casdoor itself (#350)

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
This commit is contained in:
Yixiang Zhao
2021-12-10 00:23:04 +08:00
committed by GitHub
parent 70a550d8bc
commit b73b9a65b6
5 changed files with 63 additions and 16 deletions

View File

@ -16,4 +16,3 @@ initScore = 2000
logPostOnly = true logPostOnly = true
oidcOrigin = "https://door.casbin.com" oidcOrigin = "https://door.casbin.com"
samlOrigin = "http://localhost:8000" samlOrigin = "http://localhost:8000"
samlRequestOrigin = "http://localhost:7001"

View File

@ -15,6 +15,7 @@
package controllers package controllers
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
@ -369,8 +370,14 @@ func (c *ApiController) GetSamlLogin() {
func (c *ApiController) HandleSamlLogin() { func (c *ApiController) HandleSamlLogin() {
relayState := c.Input().Get("RelayState") relayState := c.Input().Get("RelayState")
samlResponse := c.Input().Get("SAMLResponse") samlResponse := c.Input().Get("SAMLResponse")
decode, err := base64.StdEncoding.DecodeString(relayState)
if err != nil {
c.ResponseError(err.Error())
}
slice := strings.Split(string(decode), "&")
relayState = url.QueryEscape(relayState)
samlResponse = url.QueryEscape(samlResponse) samlResponse = url.QueryEscape(samlResponse)
targetUrl := fmt.Sprintf("%s/callback/saml?replayState=%s&samlResponse=%s", targetUrl := fmt.Sprintf("%s?relayState=%s&samlResponse=%s",
beego.AppConfig.String("samlRequestOrigin"), relayState, samlResponse) slice[4], relayState, samlResponse)
c.Redirect(targetUrl, 303) c.Redirect(targetUrl, 303)
} }

View File

@ -84,8 +84,8 @@ export function getSamlLogin(providerId) {
}).then(res => res.json()); }).then(res => res.json());
} }
export function loginWithSaml(values) { export function loginWithSaml(values, param) {
return fetch(`${authConfig.serverUrl}/api/login`, { return fetch(`${authConfig.serverUrl}/api/login${param}`, {
method: 'POST', method: 'POST',
credentials: "include", credentials: "include",
body: JSON.stringify(values), body: JSON.stringify(values),

View File

@ -186,8 +186,15 @@ class LoginPage extends React.Component {
} }
getSamlUrl(providerId) { getSamlUrl(providerId) {
const params = new URLSearchParams(this.props.location.search);
let clientId = params.get("client_id")
let application = params.get("state");
let realRedirectUri = params.get("redirect_uri");
let redirectUri = `${window.location.origin}/callback/saml`
let providerName = providerId.split('/')[1];
AuthBackend.getSamlLogin(providerId).then((res) => { AuthBackend.getSamlLogin(providerId).then((res) => {
window.location.href = res.data const replyState = `${clientId}&${application}&${providerName}&${realRedirectUri}&${redirectUri}`;
window.location.href = `${res.data}&RelayState=${btoa(replyState)}`;
}); });
} }

View File

@ -19,6 +19,7 @@ import * as AuthBackend from "./AuthBackend";
import * as Util from "./Util"; import * as Util from "./Util";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import i18next from "i18next"; import i18next from "i18next";
import {authConfig} from "./Auth";
class SamlCallback extends React.Component { class SamlCallback extends React.Component {
constructor(props) { constructor(props) {
@ -29,28 +30,61 @@ class SamlCallback extends React.Component {
}; };
} }
getResponseType(redirectUri) {
const authServerUrl = authConfig.serverUrl;
// Casdoor's own login page, so "code" is not necessary
if (redirectUri === "null") {
return "login";
}
const realRedirectUrl = new URL(redirectUri).origin;
// For Casdoor itself, we use "login" directly
if (authServerUrl === realRedirectUrl) {
return "login";
} else {
return "code";
}
}
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
const params = new URLSearchParams(this.props.location.search); const params = new URLSearchParams(this.props.location.search);
let relayState = params.get('relayState') let relayState = params.get('relayState')
let samlResponse = params.get('samlResponse') let samlResponse = params.get('samlResponse')
let redirectUri = `${window.location.origin}/callback`; const messages = atob(relayState).split('&');
const applicationName = "app-built-in" const clientId = messages[0];
const applicationName = messages[1] === "null" ? "app-built-in" : messages[1];
const providerName = messages[2];
const redirectUri = messages[3];
const responseType = this.getResponseType(redirectUri);
const body = { const body = {
type: "login", type: responseType,
application: applicationName, application: applicationName,
provider: "aliyun-idaas", provider: providerName,
state: applicationName, state: applicationName,
redirectUri: redirectUri, redirectUri: `${window.location.origin}/callback`,
method: "signup", method: "signup",
relayState: relayState, relayState: relayState,
samlResponse: encodeURIComponent(samlResponse), samlResponse: encodeURIComponent(samlResponse),
}; };
AuthBackend.loginWithSaml(body)
let param;
if (clientId === null || clientId === "") {
param = ""
} else {
param = `?clientId=${clientId}&responseType=${responseType}&redirectUri=${redirectUri}&scope=read&state=${applicationName}`
}
AuthBackend.loginWithSaml(body, param)
.then((res) => { .then((res) => {
if (res.status === 'ok') { if (res.status === 'ok') {
const responseType = this.getResponseType(redirectUri);
if (responseType === "login") {
Util.showMessage("success", `Logged in successfully`); Util.showMessage("success", `Logged in successfully`);
// Setting.goToLinkSoft(this, "/");
Setting.goToLink("/"); Setting.goToLink("/");
} else if (responseType === "code") {
const code = res.data;
Setting.goToLink(`${redirectUri}?code=${code}&state=${applicationName}`);
}
} else { } else {
this.setState({ this.setState({
msg: res.msg, msg: res.msg,