diff --git a/models/login_details.go b/models/login_details.go index 35ca1fead..53b319889 100644 --- a/models/login_details.go +++ b/models/login_details.go @@ -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 diff --git a/operatorapi/embedded_spec.go b/operatorapi/embedded_spec.go index 5fd059701..8e4ef8429 100644 --- a/operatorapi/embedded_spec.go +++ b/operatorapi/embedded_spec.go @@ -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" + } } } }, diff --git a/operatorapi/login.go b/operatorapi/login.go index 661f6a5e1..16ac0c8db 100644 --- a/operatorapi/login.go +++ b/operatorapi/login.go @@ -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{ diff --git a/portal-ui/src/screens/LoginPage/LoginPage.tsx b/portal-ui/src/screens/LoginPage/LoginPage.tsx index ec614a69b..a3203964d 100644 --- a/portal-ui/src/screens/LoginPage/LoginPage.tsx +++ b/portal-ui/src/screens/LoginPage/LoginPage.tsx @@ -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 = ( - + if (loginStrategy.redirect.length > 1) { + loginComponent = ( + +
Login with SSO:
+ +
+ ); + } else if (loginStrategy.redirect.length === 1) { + loginComponent = ( -
- ); + ); + } else { + loginComponent = ( +
+ Cannot retrieve redirect from login strategy +
+ ); + } break; } case loginStrategyType.serviceAccount: { diff --git a/portal-ui/src/screens/LoginPage/loginSlice.ts b/portal-ui/src/screens/LoginPage/loginSlice.ts index 0f3d03cfe..13641fc40 100644 --- a/portal-ui/src/screens/LoginPage/loginSlice.ts +++ b/portal-ui/src/screens/LoginPage/loginSlice.ts @@ -50,7 +50,8 @@ const initialState: LoginState = { jwt: "", loginStrategy: { loginStrategy: loginStrategyType.unknown, - redirect: "", + redirect: [], + displayNames: [], }, loginSending: false, loadingFetchConfiguration: true, diff --git a/portal-ui/src/screens/LoginPage/types.ts b/portal-ui/src/screens/LoginPage/types.ts index 008d6408c..5a8301741 100644 --- a/portal-ui/src/screens/LoginPage/types.ts +++ b/portal-ui/src/screens/LoginPage/types.ts @@ -16,7 +16,8 @@ export interface ILoginDetails { loginStrategy: loginStrategyType; - redirect: string; + redirect: string[]; + displayNames: string[]; isDirectPV?: boolean; } diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index f79329c86..13032c538 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -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" + } } } }, diff --git a/restapi/user_login.go b/restapi/user_login.go index f304d0b81..29c33baea 100644 --- a/restapi/user_login.go +++ b/restapi/user_login.go @@ -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 } diff --git a/sso-integration/dex-requests.py b/sso-integration/dex-requests.py index d9d8b4a60..8f9aa8304 100644 --- a/sso-integration/dex-requests.py +++ b/sso-integration/dex-requests.py @@ -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]) diff --git a/sso-integration/sso_test.go b/sso-integration/sso_test.go index e87e5b448..bfee20fbc 100644 --- a/sso-integration/sso_test.go +++ b/sso-integration/sso_test.go @@ -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 { diff --git a/swagger-console.yml b/swagger-console.yml index 4ba803828..125b2e2b6 100644 --- a/swagger-console.yml +++ b/swagger-console.yml @@ -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: diff --git a/swagger-operator.yml b/swagger-operator.yml index d3842e3b2..deb03957c 100644 --- a/swagger-operator.yml +++ b/swagger-operator.yml @@ -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