Add support for Multiple IDPs on Login screen (#2258)

This commit is contained in:
Kaan Kabalak
2022-09-03 19:02:48 +03:00
committed by GitHub
parent 7702149962
commit 035a5b88c2
12 changed files with 180 additions and 38 deletions

View File

@@ -37,6 +37,9 @@ import (
// swagger:model loginDetails
type LoginDetails struct {
// display names
DisplayNames []string `json:"displayNames"`
// is direct p v
IsDirectPV bool `json:"isDirectPV,omitempty"`
@@ -45,7 +48,7 @@ type LoginDetails struct {
LoginStrategy string `json:"loginStrategy,omitempty"`
// redirect
Redirect string `json:"redirect,omitempty"`
Redirect []string `json:"redirect"`
}
// Validate validates this login details

View File

@@ -3530,6 +3530,12 @@ func init() {
"loginDetails": {
"type": "object",
"properties": {
"displayNames": {
"type": "array",
"items": {
"type": "string"
}
},
"isDirectPV": {
"type": "boolean"
},
@@ -3543,7 +3549,10 @@ func init() {
]
},
"redirect": {
"type": "string"
"type": "array",
"items": {
"type": "string"
}
}
}
},
@@ -9434,6 +9443,12 @@ func init() {
"loginDetails": {
"type": "object",
"properties": {
"displayNames": {
"type": "array",
"items": {
"type": "string"
}
},
"isDirectPV": {
"type": "boolean"
},
@@ -9447,7 +9462,10 @@ func init() {
]
},
"redirect": {
"type": "string"
"type": "array",
"items": {
"type": "string"
}
}
}
},

View File

@@ -101,7 +101,7 @@ func getLoginDetailsResponse(params authApi.LoginDetailParams) (*models.LoginDet
r := params.HTTPRequest
loginStrategy := models.LoginDetailsLoginStrategyServiceDashAccount
redirectURL := ""
redirectURL := []string{}
if oauth2.IsIDPEnabled() {
loginStrategy = models.LoginDetailsLoginStrategyRedirectDashServiceDashAccount
@@ -112,7 +112,7 @@ func getLoginDetailsResponse(params authApi.LoginDetailParams) (*models.LoginDet
}
// Validate user against IDP
identityProvider := &auth.IdentityProvider{Client: oauth2Client}
redirectURL = identityProvider.GenerateLoginURL()
redirectURL = append(redirectURL, identityProvider.GenerateLoginURL())
}
loginDetails := &models.LoginDetails{

View File

@@ -17,13 +17,20 @@
import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Box, InputAdornment, LinearProgress } from "@mui/material";
import {
Box,
InputAdornment,
LinearProgress,
Select,
MenuItem,
} from "@mui/material";
import { Theme, useTheme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import makeStyles from "@mui/styles/makeStyles";
import Button from "@mui/material/Button";
import Grid from "@mui/material/Grid";
import { loginStrategyType } from "./types";
import LogoutIcon from "../../icons/LogoutIcon";
import RefreshIcon from "../../icons/RefreshIcon";
import MainError from "../Console/Common/MainError/MainError";
import {
@@ -72,6 +79,37 @@ const useStyles = makeStyles((theme: Theme) =>
boxShadow: "none",
padding: "16px 30px",
},
loginSsoText: {
fontWeight: "700",
marginBottom: "15px",
},
ssoSelect: {
width: "100%",
fontSize: "13px",
fontWeight: "700",
color: "grey",
},
ssoMenuItem: {
fontSize: "15px",
fontWeight: "700",
color: theme.palette.primary.light,
"&.MuiMenuItem-divider:last-of-type": {
borderBottom: "none",
},
"&.Mui-focusVisible": {
backgroundColor: theme.palette.grey["100"],
},
},
ssoLoginIcon: {
height: "13px",
marginRight: "25px",
},
ssoSubmit: {
marginTop: "15px",
"&:first-of-type": {
marginTop: 0,
},
},
separator: {
marginLeft: 8,
marginRight: 8,
@@ -189,6 +227,9 @@ const useStyles = makeStyles((theme: Theme) =>
},
},
},
loginStrategyMessage: {
textAlign: "center",
},
loadingLoginStrategy: {
textAlign: "center",
width: 40,
@@ -298,21 +339,59 @@ const Login = () => {
}
case loginStrategyType.redirect:
case loginStrategyType.redirectServiceAccount: {
loginComponent = (
<React.Fragment>
if (loginStrategy.redirect.length > 1) {
loginComponent = (
<React.Fragment>
<div className={classes.loginSsoText}>Login with SSO:</div>
<Select
id="ssoLogin"
name="ssoLogin"
data-test-id="sso-login"
onChange={(e) => {
if (e.target.value) {
window.location.href = e.target.value as string;
}
}}
displayEmpty
className={classes.ssoSelect}
renderValue={() => "Select Provider"}
>
{loginStrategy.redirect.map((r, idx) => (
<MenuItem
value={r}
key={`sso-login-option-${idx}`}
className={classes.ssoMenuItem}
divider={true}
>
<LogoutIcon className={classes.ssoLoginIcon} />
{loginStrategy.displayNames[idx]}
</MenuItem>
))}
</Select>
</React.Fragment>
);
} else if (loginStrategy.redirect.length === 1) {
loginComponent = (
<Button
key={`login-button`}
component={"a"}
href={loginStrategy.redirect}
href={loginStrategy.redirect[0]}
type="submit"
variant="contained"
color="primary"
id="sso-login"
className={classes.submit}
className={clsx(classes.submit, classes.ssoSubmit)}
>
Login with SSO
{loginStrategy.displayNames[0]}
</Button>
</React.Fragment>
);
);
} else {
loginComponent = (
<div className={classes.loginStrategyMessage}>
Cannot retrieve redirect from login strategy
</div>
);
}
break;
}
case loginStrategyType.serviceAccount: {

View File

@@ -50,7 +50,8 @@ const initialState: LoginState = {
jwt: "",
loginStrategy: {
loginStrategy: loginStrategyType.unknown,
redirect: "",
redirect: [],
displayNames: [],
},
loginSending: false,
loadingFetchConfiguration: true,

View File

@@ -16,7 +16,8 @@
export interface ILoginDetails {
loginStrategy: loginStrategyType;
redirect: string;
redirect: string[];
displayNames: string[];
isDirectPV?: boolean;
}

View File

@@ -5423,6 +5423,12 @@ func init() {
"loginDetails": {
"type": "object",
"properties": {
"displayNames": {
"type": "array",
"items": {
"type": "string"
}
},
"loginStrategy": {
"type": "string",
"enum": [
@@ -5433,7 +5439,10 @@ func init() {
]
},
"redirect": {
"type": "string"
"type": "array",
"items": {
"type": "string"
}
}
}
},
@@ -12692,6 +12701,12 @@ func init() {
"loginDetails": {
"type": "object",
"properties": {
"displayNames": {
"type": "array",
"items": {
"type": "string"
}
},
"loginStrategy": {
"type": "string",
"enum": [
@@ -12702,7 +12717,10 @@ func init() {
]
},
"redirect": {
"type": "string"
"type": "array",
"items": {
"type": "string"
}
}
}
},

View File

@@ -149,23 +149,32 @@ func getLoginDetailsResponse(params authApi.LoginDetailParams, openIDProviders o
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
loginStrategy := models.LoginDetailsLoginStrategyForm
redirectURL := ""
redirectURL := []string{}
displayNames := []string{}
r := params.HTTPRequest
if len(openIDProviders) > 0 {
var loginDetails *models.LoginDetails
if len(openIDProviders) >= 1 {
loginStrategy = models.LoginDetailsLoginStrategyRedirect
// initialize new oauth2 client
oauth2Client, err := openIDProviders.NewOauth2ProviderClient(idpName, nil, r, GetConsoleHTTPClient())
if err != nil {
return nil, ErrorWithContext(ctx, err, ErrOauth2Provider)
for name, provider := range openIDProviders {
// initialize new oauth2 client
oauth2Client, err := openIDProviders.NewOauth2ProviderClient(name, nil, r, GetConsoleHTTPClient())
if err != nil {
return nil, ErrorWithContext(ctx, err, ErrOauth2Provider)
}
// Validate user against IDP
identityProvider := &auth.IdentityProvider{Client: oauth2Client}
redirectURL = append(redirectURL, identityProvider.GenerateLoginURL())
if provider.DisplayName != "" {
displayNames = append(displayNames, provider.DisplayName)
} else {
displayNames = append(displayNames, "Login with SSO")
}
}
// Validate user against IDP
identityProvider := &auth.IdentityProvider{Client: oauth2Client}
redirectURL = identityProvider.GenerateLoginURL()
}
loginDetails := &models.LoginDetails{
loginDetails = &models.LoginDetails{
LoginStrategy: loginStrategy,
Redirect: redirectURL,
DisplayNames: displayNames,
}
return loginDetails, nil
}

View File

@@ -1,9 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pdb, sys, requests, pdb
import pdb, sys, requests
from bs4 import BeautifulSoup
from urllib.parse import unquote
# Log in to Your Account via OpenLDAP Connector
result = requests.get(sys.argv[1])

View File

@@ -130,19 +130,20 @@ func TestMain(t *testing.T) {
if err != nil {
log.Fatal(err)
}
var jsonMap map[string]interface{}
var jsonMap map[string][]interface{}
json.Unmarshal(body, &jsonMap)
fmt.Println(jsonMap["redirect"])
redirect := jsonMap["redirect"]
fmt.Println(jsonMap["redirect"][0])
redirect := jsonMap["redirect"][0]
redirectAsString := fmt.Sprint(redirect)
fmt.Println(redirectAsString)
// execute script to get the code and state
cmd, err := exec.Command("python3", "dex-requests.py", redirectAsString).Output()
if err != nil {
fmt.Printf("error %s", err)
fmt.Printf("error %s\n", err)
}
urlOutput := string(cmd)
fmt.Println("url output:", urlOutput)
requestLoginBody := bytes.NewReader([]byte("login=dillon%40example.io&password=dillon"))
// parse url remove carriage return
@@ -163,7 +164,9 @@ func TestMain(t *testing.T) {
urlOutput,
requestLoginBody,
)
fmt.Println(newRequestError)
if newRequestError != nil {
fmt.Println(newRequestError)
}
httpRequestLogin.Header.Add("Content-Type", "application/x-www-form-urlencoded")
responseLogin, errorLogin := client.Do(httpRequestLogin)
if errorLogin != nil {

View File

@@ -3685,7 +3685,13 @@ definitions:
type: string
enum: [ form, redirect, service-account, redirect-service-account ]
redirect:
type: string
type: array
items:
type: string
displayNames:
type: array
items:
type: string
loginOauth2AuthRequest:
type: object
required:

View File

@@ -1589,7 +1589,13 @@ definitions:
type: string
enum: [ form, redirect, service-account, redirect-service-account ]
redirect:
type: string
type: array
items:
type: string
displayNames:
type: array
items:
type: string
isDirectPV:
type: boolean
loginRequest:
@@ -2600,7 +2606,6 @@ definitions:
type: object
requests:
additionalProperties:
additionalProperties:
type: integer
format: int64
description: "Requests describes the minimum amount of compute