Fixes to Multiple IDP support in console (#2392)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2022-10-20 20:08:54 -05:00
committed by GitHub
parent 139e90830f
commit dab4eb7664
19 changed files with 380 additions and 112 deletions

View File

@@ -25,6 +25,7 @@ package models
import (
"context"
"encoding/json"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -37,9 +38,6 @@ import (
// swagger:model loginDetails
type LoginDetails struct {
// display names
DisplayNames []string `json:"displayNames"`
// is direct p v
IsDirectPV bool `json:"isDirectPV,omitempty"`
@@ -47,8 +45,8 @@ type LoginDetails struct {
// Enum: [form redirect service-account redirect-service-account]
LoginStrategy string `json:"loginStrategy,omitempty"`
// redirect
Redirect []string `json:"redirect"`
// redirect rules
RedirectRules []*RedirectRule `json:"redirectRules"`
}
// Validate validates this login details
@@ -59,6 +57,10 @@ func (m *LoginDetails) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateRedirectRules(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
@@ -113,8 +115,63 @@ func (m *LoginDetails) validateLoginStrategy(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this login details based on context it is used
func (m *LoginDetails) validateRedirectRules(formats strfmt.Registry) error {
if swag.IsZero(m.RedirectRules) { // not required
return nil
}
for i := 0; i < len(m.RedirectRules); i++ {
if swag.IsZero(m.RedirectRules[i]) { // not required
continue
}
if m.RedirectRules[i] != nil {
if err := m.RedirectRules[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("redirectRules" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("redirectRules" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this login details based on the context it is used
func (m *LoginDetails) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateRedirectRules(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *LoginDetails) contextValidateRedirectRules(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.RedirectRules); i++ {
if m.RedirectRules[i] != nil {
if err := m.RedirectRules[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("redirectRules" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("redirectRules" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}

70
models/redirect_rule.go Normal file
View File

@@ -0,0 +1,70 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// RedirectRule redirect rule
//
// swagger:model redirectRule
type RedirectRule struct {
// display name
DisplayName string `json:"displayName,omitempty"`
// redirect
Redirect string `json:"redirect,omitempty"`
}
// Validate validates this redirect rule
func (m *RedirectRule) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this redirect rule based on context it is used
func (m *RedirectRule) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *RedirectRule) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *RedirectRule) UnmarshalBinary(b []byte) error {
var res RedirectRule
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -3623,12 +3623,6 @@ func init() {
"loginDetails": {
"type": "object",
"properties": {
"displayNames": {
"type": "array",
"items": {
"type": "string"
}
},
"isDirectPV": {
"type": "boolean"
},
@@ -3641,10 +3635,10 @@ func init() {
"redirect-service-account"
]
},
"redirect": {
"redirectRules": {
"type": "array",
"items": {
"type": "string"
"$ref": "#/definitions/redirectRule"
}
}
}
@@ -4396,6 +4390,17 @@ func init() {
}
}
},
"redirectRule": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"redirect": {
"type": "string"
}
}
},
"resourceQuota": {
"type": "object",
"properties": {
@@ -9614,12 +9619,6 @@ func init() {
"loginDetails": {
"type": "object",
"properties": {
"displayNames": {
"type": "array",
"items": {
"type": "string"
}
},
"isDirectPV": {
"type": "boolean"
},
@@ -9632,10 +9631,10 @@ func init() {
"redirect-service-account"
]
},
"redirect": {
"redirectRules": {
"type": "array",
"items": {
"type": "string"
"$ref": "#/definitions/redirectRule"
}
}
}
@@ -10252,6 +10251,17 @@ func init() {
}
}
},
"redirectRule": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"redirect": {
"type": "string"
}
}
},
"resourceQuota": {
"type": "object",
"properties": {

View File

@@ -101,7 +101,8 @@ func getLoginDetailsResponse(params authApi.LoginDetailParams) (*models.LoginDet
r := params.HTTPRequest
loginStrategy := models.LoginDetailsLoginStrategyServiceDashAccount
redirectURL := []string{}
var redirectRules []*models.RedirectRule
if oauth2.IsIDPEnabled() {
loginStrategy = models.LoginDetailsLoginStrategyRedirectDashServiceDashAccount
@@ -115,12 +116,18 @@ func getLoginDetailsResponse(params authApi.LoginDetailParams) (*models.LoginDet
KeyFunc: oauth2.DefaultDerivedKey,
Client: oauth2Client,
}
redirectURL = append(redirectURL, identityProvider.GenerateLoginURL())
newRedirectRule := &models.RedirectRule{
Redirect: identityProvider.GenerateLoginURL(),
DisplayName: "Login with SSO",
}
redirectRules = append(redirectRules, newRedirectRule)
}
loginDetails := &models.LoginDetails{
LoginStrategy: loginStrategy,
Redirect: redirectURL,
RedirectRules: redirectRules,
IsDirectPV: getDirectPVEnabled(),
}
return loginDetails, nil

View File

@@ -40,11 +40,12 @@ type IdentityProviderI interface {
type IdentityProvider struct {
KeyFunc oauth2.StateKeyFunc
Client *oauth2.Provider
RoleARN string
}
// VerifyIdentity will verify the user identity against the idp using the authorization code flow
func (c IdentityProvider) VerifyIdentity(ctx context.Context, code, state string) (*credentials.Credentials, error) {
return c.Client.VerifyIdentity(ctx, code, state, c.KeyFunc)
return c.Client.VerifyIdentity(ctx, code, state, c.RoleARN, c.KeyFunc)
}
// VerifyIdentityForOperator will verify the user identity against the idp using the authorization code flow
@@ -54,5 +55,5 @@ func (c IdentityProvider) VerifyIdentityForOperator(ctx context.Context, code, s
// GenerateLoginURL returns a new URL used by the user to login against the idp
func (c IdentityProvider) GenerateLoginURL() string {
return c.Client.GenerateLoginURL(c.KeyFunc)
return c.Client.GenerateLoginURL(c.KeyFunc, c.Client.IDPName)
}

View File

@@ -48,9 +48,11 @@ func (pc ProviderConfig) GetStateKeyFunc() StateKeyFunc {
}
}
type OpenIDPCfg map[string]ProviderConfig
func (pc ProviderConfig) GetARNInf() string {
return pc.RoleArn
}
var DefaultIDPConfig = "_"
type OpenIDPCfg map[string]ProviderConfig
func GetSTSEndpoint() string {
return strings.TrimSpace(env.Get(ConsoleMinIOServer, "http://localhost:9000"))

View File

@@ -29,4 +29,5 @@ const (
ConsoleIDPScopes = "CONSOLE_IDP_SCOPES"
ConsoleIDPUserInfo = "CONSOLE_IDP_USERINFO"
ConsoleIDPTokenExpiration = "CONSOLE_IDP_TOKEN_EXPIRATION"
ConsoleIDPRoleARN = "CONSOLE_IDP_ROLE_ARN"
)

View File

@@ -92,13 +92,13 @@ func (ac Config) TokenSource(ctx context.Context, t *xoauth2.Token) xoauth2.Toke
type Provider struct {
// oauth2Config is an interface configuration that contains the following fields
// Config{
// ClientID string
// IDPName string
// ClientSecret string
// RedirectURL string
// Endpoint oauth2.Endpoint
// Scopes []string
// }
// - ClientID is the public identifier for this application
// - IDPName is the public identifier for this application
// - ClientSecret is a shared secret between this application and the authorization server
// - RedirectURL is the URL to redirect users going through
// the OAuth flow, after the resource owner's URLs.
@@ -107,7 +107,7 @@ type Provider struct {
// often available via site-specific packages, such as
// google.Endpoint or github.Endpoint.
// - Scopes specifies optional requested permissions.
ClientID string
IDPName string
// if enabled means that we need extrace access_token as well
UserInfo bool
oauth2Config Configuration
@@ -178,6 +178,7 @@ func NewOauth2ProviderClient(scopes []string, r *http.Request, httpClient *http.
}
redirectURL := GetIDPCallbackURL()
if GetIDPCallbackURLDynamic() {
// dynamic redirect if set, will generate redirect URLs
// dynamically based on incoming requests.
@@ -199,7 +200,7 @@ func NewOauth2ProviderClient(scopes []string, r *http.Request, httpClient *http.
Scopes: scopes,
}
client.ClientID = GetIDPClientID()
client.IDPName = GetIDPClientID()
client.UserInfo = GetIDPUserInfo()
client.provHTTPClient = httpClient
@@ -273,7 +274,7 @@ func (o OpenIDPCfg) NewOauth2ProviderClient(name string, scopes []string, r *htt
Scopes: scopes,
}
client.ClientID = o[name].ClientID
client.IDPName = name
client.UserInfo = o[name].Userinfo
client.provHTTPClient = httpClient
return client, nil
@@ -310,9 +311,10 @@ type StateKeyFunc func() []byte
// VerifyIdentity will contact the configured IDP to the user identity based on the authorization code and state
// if the user is valid, then it will contact MinIO to get valid sts credentials based on the identity provided by the IDP
func (client *Provider) VerifyIdentity(ctx context.Context, code, state string, keyFunc StateKeyFunc) (*credentials.Credentials, error) {
func (client *Provider) VerifyIdentity(ctx context.Context, code, state, roleARN string, keyFunc StateKeyFunc) (*credentials.Credentials, error) {
// verify the provided state is valid (prevents CSRF attacks)
if err := validateOauth2State(state, keyFunc); err != nil {
fmt.Println("err1", err)
return nil, err
}
getWebTokenExpiry := func() (*credentials.WebIdentityToken, error) {
@@ -352,10 +354,12 @@ func (client *Provider) VerifyIdentity(ctx context.Context, code, state string,
return token, nil
}
stsEndpoint := GetSTSEndpoint()
sts := credentials.New(&credentials.STSWebIdentity{
Client: client.provHTTPClient,
STSEndpoint: stsEndpoint,
GetWebIDTokenExpiry: getWebTokenExpiry,
RoleARN: roleARN,
})
return sts, nil
}
@@ -439,10 +443,34 @@ func GetRandomStateWithHMAC(length int, keyFunc StateKeyFunc) string {
return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", state, hmac)))
}
type LoginURLParams struct {
State string `json:"state"`
IDPName string `json:"idp_name"`
}
// GenerateLoginURL returns a new login URL based on the configured IDP
func (client *Provider) GenerateLoginURL(keyFunc StateKeyFunc) string {
func (client *Provider) GenerateLoginURL(keyFunc StateKeyFunc, iDPName string) string {
// generates random state and sign it using HMAC256
state := GetRandomStateWithHMAC(25, keyFunc)
loginURL := client.oauth2Config.AuthCodeURL(state)
configureID := "_"
if iDPName != "" {
configureID = iDPName
}
lgParams := LoginURLParams{
State: state,
IDPName: configureID,
}
jsonEnc, err := json.Marshal(lgParams)
if err != nil {
return ""
}
stEncode := base64.StdEncoding.EncodeToString(jsonEnc)
loginURL := client.oauth2Config.AuthCodeURL(stEncode)
return strings.TrimSpace(loginURL)
}

View File

@@ -66,6 +66,6 @@ func TestGenerateLoginURL(t *testing.T) {
// a non-empty string
return state
}
url := oauth2Provider.GenerateLoginURL(DefaultDerivedKey)
url := oauth2Provider.GenerateLoginURL(DefaultDerivedKey, "testIDP")
funcAssert.NotEqual("", url)
}

View File

@@ -15,21 +15,20 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import {
Box,
InputAdornment,
LinearProgress,
Select,
MenuItem,
Select,
} from "@mui/material";
import { Button } from "mds";
import { Theme, useTheme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import makeStyles from "@mui/styles/makeStyles";
import Grid from "@mui/material/Grid";
import { loginStrategyType } from "./types";
import { loginStrategyType, redirectRule } from "./types";
import LogoutIcon from "../../icons/LogoutIcon";
import RefreshIcon from "../../icons/RefreshIcon";
import MainError from "../Console/Common/MainError/MainError";
@@ -58,6 +57,7 @@ import { resetForm, setJwt } from "./loginSlice";
import StrategyForm from "./StrategyForm";
import { LoginField } from "./LoginField";
import DirectPVLogo from "../../icons/DirectPVLogo";
import { redirectRules } from "../../utils/sortFunctions";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -344,7 +344,19 @@ const Login = () => {
}
case loginStrategyType.redirect:
case loginStrategyType.redirectServiceAccount: {
if (loginStrategy.redirect.length > 1) {
let redirectItems: redirectRule[] = [];
if (
loginStrategy.redirectRules &&
loginStrategy.redirectRules.length > 0
) {
redirectItems = [...loginStrategy.redirectRules].sort(redirectRules);
}
if (
loginStrategy.redirectRules &&
loginStrategy.redirectRules.length > 1
) {
loginComponent = (
<React.Fragment>
<div className={classes.loginSsoText}>Login with SSO:</div>
@@ -361,21 +373,21 @@ const Login = () => {
className={classes.ssoSelect}
renderValue={() => "Select Provider"}
>
{loginStrategy.redirect.map((r, idx) => (
{redirectItems.map((r, idx) => (
<MenuItem
value={r}
value={r.redirect}
key={`sso-login-option-${idx}`}
className={classes.ssoMenuItem}
divider={true}
>
<LogoutIcon className={classes.ssoLoginIcon} />
{loginStrategy.displayNames[idx]}
{r.displayName}
</MenuItem>
))}
</Select>
</React.Fragment>
);
} else if (loginStrategy.redirect.length === 1) {
} else if (redirectItems.length === 1) {
loginComponent = (
<div className={clsx(classes.submit, classes.ssoSubmit)}>
<Button
@@ -383,12 +395,11 @@ const Login = () => {
variant="callAction"
id="sso-login"
label={
loginStrategy.displayNames &&
loginStrategy.displayNames.length > 0
? loginStrategy.displayNames[0]
: "Login with SSO"
redirectItems[0].displayName === ""
? "Login with SSO"
: redirectItems[0].displayName
}
onClick={() => (window.location.href = loginStrategy.redirect[0])}
onClick={() => (window.location.href = redirectItems[0].redirect)}
fullWidth
/>
</div>

View File

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

View File

@@ -16,11 +16,15 @@
export interface ILoginDetails {
loginStrategy: loginStrategyType;
redirect: string[];
displayNames: string[];
redirectRules: redirectRule[];
isDirectPV?: boolean;
}
export interface redirectRule {
redirect: string;
displayName: string;
}
export enum loginStrategyType {
unknown = "unknown",
form = "form",

View File

@@ -14,6 +14,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { redirectRule } from "../screens/LoginPage/types";
interface userInterface {
accessKey: string;
}
@@ -72,3 +74,13 @@ export const policyDetailsSort = (
// a must be equal to b
return 0;
};
export const redirectRules = (a: redirectRule, b: redirectRule) => {
if (a.displayName > b.displayName) {
return 1;
}
if (a.displayName < b.displayName) {
return -1;
}
return 0;
};

View File

@@ -6349,12 +6349,6 @@ func init() {
"loginDetails": {
"type": "object",
"properties": {
"displayNames": {
"type": "array",
"items": {
"type": "string"
}
},
"isDirectPV": {
"type": "boolean"
},
@@ -6367,10 +6361,10 @@ func init() {
"redirect-service-account"
]
},
"redirect": {
"redirectRules": {
"type": "array",
"items": {
"type": "string"
"$ref": "#/definitions/redirectRule"
}
}
}
@@ -7038,6 +7032,17 @@ func init() {
}
}
},
"redirectRule": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"redirect": {
"type": "string"
}
}
},
"remoteBucket": {
"type": "object",
"required": [
@@ -14605,12 +14610,6 @@ func init() {
"loginDetails": {
"type": "object",
"properties": {
"displayNames": {
"type": "array",
"items": {
"type": "string"
}
},
"isDirectPV": {
"type": "boolean"
},
@@ -14623,10 +14622,10 @@ func init() {
"redirect-service-account"
]
},
"redirect": {
"redirectRules": {
"type": "array",
"items": {
"type": "string"
"$ref": "#/definitions/redirectRule"
}
}
}
@@ -15294,6 +15293,17 @@ func init() {
}
}
},
"redirectRule": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"redirect": {
"type": "string"
}
}
},
"remoteBucket": {
"type": "object",
"required": [

View File

@@ -66,7 +66,6 @@ func replaceJwtVariables(rawPolicy []byte, claims map[string]interface{}) json.R
for _, field := range jwtFields {
if val, ok := claims[field]; ok {
variable := fmt.Sprintf("${jwt:%s}", field)
fmt.Println("found", variable)
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte(variable), []byte(fmt.Sprintf("%v", val)))
}
}

View File

@@ -18,11 +18,10 @@ package restapi
import (
"context"
"encoding/base64"
"encoding/json"
"net/http"
"github.com/minio/madmin-go"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
@@ -30,12 +29,14 @@ import (
"github.com/minio/console/pkg/auth/idp/oauth2"
"github.com/minio/console/restapi/operations"
authApi "github.com/minio/console/restapi/operations/auth"
"github.com/minio/madmin-go"
"github.com/minio/minio-go/v7/pkg/credentials"
)
func registerLoginHandlers(api *operations.ConsoleAPI) {
// GET login strategy
api.AuthLoginDetailHandler = authApi.LoginDetailHandlerFunc(func(params authApi.LoginDetailParams) middleware.Responder {
loginDetails, err := getLoginDetailsResponse(params, GlobalMinIOConfig.OpenIDProviders, oauth2.DefaultIDPConfig)
loginDetails, err := getLoginDetailsResponse(params, GlobalMinIOConfig.OpenIDProviders)
if err != nil {
return authApi.NewLoginDetailDefault(int(err.Code)).WithPayload(err)
}
@@ -56,7 +57,7 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
})
// POST login using external IDP
api.AuthLoginOauth2AuthHandler = authApi.LoginOauth2AuthHandlerFunc(func(params authApi.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params, GlobalMinIOConfig.OpenIDProviders, oauth2.DefaultIDPConfig)
loginResponse, err := getLoginOauth2AuthResponse(params, GlobalMinIOConfig.OpenIDProviders)
if err != nil {
return authApi.NewLoginOauth2AuthDefault(int(err.Code)).WithPayload(err)
}
@@ -145,12 +146,12 @@ func getLoginResponse(params authApi.LoginParams) (*models.LoginResponse, *model
}
// getLoginDetailsResponse returns information regarding the Console authentication mechanism.
func getLoginDetailsResponse(params authApi.LoginDetailParams, openIDProviders oauth2.OpenIDPCfg, idpName string) (*models.LoginDetails, *models.Error) {
func getLoginDetailsResponse(params authApi.LoginDetailParams, openIDProviders oauth2.OpenIDPCfg) (*models.LoginDetails, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
loginStrategy := models.LoginDetailsLoginStrategyForm
redirectURL := []string{}
displayNames := []string{}
var redirectRules []*models.RedirectRule
r := params.HTTPRequest
var loginDetails *models.LoginDetails
if len(openIDProviders) >= 1 {
@@ -166,18 +167,24 @@ func getLoginDetailsResponse(params authApi.LoginDetailParams, openIDProviders o
KeyFunc: provider.GetStateKeyFunc(),
Client: oauth2Client,
}
redirectURL = append(redirectURL, identityProvider.GenerateLoginURL())
displayName := "Login with SSO"
if provider.DisplayName != "" {
displayNames = append(displayNames, provider.DisplayName)
} else {
displayNames = append(displayNames, "Login with SSO")
displayName = provider.DisplayName
}
redirectRule := models.RedirectRule{
Redirect: identityProvider.GenerateLoginURL(),
DisplayName: displayName,
}
redirectRules = append(redirectRules, &redirectRule)
}
}
loginDetails = &models.LoginDetails{
LoginStrategy: loginStrategy,
Redirect: redirectURL,
DisplayNames: displayNames,
RedirectRules: redirectRules,
}
return loginDetails, nil
}
@@ -187,29 +194,50 @@ func verifyUserAgainstIDP(ctx context.Context, provider auth.IdentityProviderI,
userCredentials, err := provider.VerifyIdentity(ctx, code, state)
if err != nil {
LogError("error validating user identity against idp: %v", err)
return nil, ErrInvalidLogin
return nil, err
}
return userCredentials, nil
}
func getLoginOauth2AuthResponse(params authApi.LoginOauth2AuthParams, openIDProviders oauth2.OpenIDPCfg, idpName string) (*models.LoginResponse, *models.Error) {
func getLoginOauth2AuthResponse(params authApi.LoginOauth2AuthParams, openIDProviders oauth2.OpenIDPCfg) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
r := params.HTTPRequest
lr := params.Body
if openIDProviders != nil {
// initialize new oauth2 client
oauth2Client, err := openIDProviders.NewOauth2ProviderClient(idpName, nil, r, GetConsoleHTTPClient(""))
// we read state
rState := *lr.State
decodedRState, err := base64.StdEncoding.DecodeString(rState)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
var requestItems oauth2.LoginURLParams
err = json.Unmarshal(decodedRState, &requestItems)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
IDPName := requestItems.IDPName
state := requestItems.State
providerCfg := openIDProviders[IDPName]
oauth2Client, err := openIDProviders.NewOauth2ProviderClient(IDPName, nil, r, GetConsoleHTTPClient(""))
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
// initialize new identity provider
identityProvider := auth.IdentityProvider{
KeyFunc: openIDProviders[idpName].GetStateKeyFunc(),
KeyFunc: providerCfg.GetStateKeyFunc(),
Client: oauth2Client,
RoleARN: providerCfg.RoleArn,
}
// Validate user against IDP
userCredentials, err := verifyUserAgainstIDP(ctx, identityProvider, *lr.Code, *lr.State)
userCredentials, err := verifyUserAgainstIDP(ctx, identityProvider, *lr.Code, state)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}

View File

@@ -18,6 +18,7 @@ package ssointegration
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
@@ -44,7 +45,7 @@ var token string
func initConsoleServer(consoleIDPURL string) (*restapi.Server, error) {
// Configure Console Server with vars to get the idp config from the container
pcfg := map[string]consoleoauth2.ProviderConfig{
consoleoauth2.DefaultIDPConfig: {
"_": {
URL: consoleIDPURL,
ClientID: "minio-client-app",
ClientSecret: "minio-client-app-secret",
@@ -130,11 +131,18 @@ func TestMain(t *testing.T) {
if err != nil {
log.Fatal(err)
}
var jsonMap map[string][]interface{}
json.Unmarshal(body, &jsonMap)
fmt.Println(jsonMap["redirect"][0])
redirect := jsonMap["redirect"][0]
redirectAsString := fmt.Sprint(redirect)
var jsonMap models.LoginDetails
fmt.Println(body)
err = json.Unmarshal(body, &jsonMap)
if err != nil {
fmt.Printf("error JSON Unmarshal %s\n", err)
}
redirectRule := jsonMap.RedirectRules[0]
redirectAsString := fmt.Sprint(redirectRule.Redirect)
fmt.Println(redirectAsString)
// execute script to get the code and state
@@ -238,12 +246,25 @@ func TestBadLogin(t *testing.T) {
Timeout: 2 * time.Second,
}
encodeItem := consoleoauth2.LoginURLParams{
State: "invalidState",
IDPName: "_",
}
jsonState, err := json.Marshal(encodeItem)
if err != nil {
log.Println(err)
assert.Nil(err)
}
// get login credentials
stateVarIable := base64.StdEncoding.EncodeToString(jsonState)
codeVarIable := "invalidCode"
stateVarIabl := "invalidState"
requestData := map[string]string{
"code": codeVarIable,
"state": stateVarIabl,
"state": stateVarIable,
}
requestDataJSON, _ := json.Marshal(requestData)

View File

@@ -4045,14 +4045,10 @@ definitions:
loginStrategy:
type: string
enum: [ form, redirect, service-account, redirect-service-account ]
redirect:
redirectRules:
type: array
items:
type: string
displayNames:
type: array
items:
type: string
$ref: "#/definitions/redirectRule"
isDirectPV:
type: boolean
loginOauth2AuthRequest:
@@ -5567,3 +5563,11 @@ definitions:
type: integer
maxConcurrentDownloads:
type: integer
redirectRule:
type: object
properties:
redirect:
type: string
displayName:
type: string

View File

@@ -1639,14 +1639,10 @@ definitions:
loginStrategy:
type: string
enum: [form, redirect, service-account, redirect-service-account]
redirect:
redirectRules:
type: array
items:
type: string
displayNames:
type: array
items:
type: string
$ref: "#/definitions/redirectRule"
isDirectPV:
type: boolean
loginRequest:
@@ -3745,3 +3741,11 @@ definitions:
properties:
registered:
type: boolean
redirectRule:
type: object
properties:
redirect:
type: string
displayName:
type: string