idp integration for mcs (#75)

This PR adds support for oidc in  mcs, to enable idp
authentication you need to pass the following environment variables and
restart mcs.

```
MCS_IDP_URL=""
MCS_IDP_CLIENT_ID=""
MCS_IDP_SECRET=""
MCS_IDP_CALLBACK=""
```
This commit is contained in:
Lenin Alevski
2020-05-01 08:38:52 -07:00
committed by GitHub
parent f3d7e61ddb
commit 44d8e9b975
36 changed files with 2276 additions and 80 deletions

View File

@@ -22,7 +22,7 @@ assets:
test:
@(go test -race -v github.com/minio/mcs/restapi/...)
@(go test -race -v github.com/minio/mcs/pkg/auth)
@(go test -race -v github.com/minio/mcs/pkg/auth/...)
coverage:
@(go test -v -coverprofile=coverage.out github.com/minio/mcs/restapi/... && go tool cover -html=coverage.out && open coverage.html)

3
go.mod
View File

@@ -3,6 +3,7 @@ module github.com/minio/mcs
go 1.14
require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/elazarl/go-bindata-assetfs v1.0.0
github.com/go-openapi/errors v0.19.4
@@ -19,9 +20,11 @@ require (
github.com/minio/mc v0.0.0-20200415193718-68b638f2f96c
github.com/minio/minio v0.0.0-20200428222040-c3c3e9087bc1
github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/satori/go.uuid v1.2.0
github.com/stretchr/testify v1.5.1
github.com/unrolled/secure v1.0.7
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
)

4
go.sum
View File

@@ -69,6 +69,8 @@ github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.12+incompatible h1:pAWNwdf7QiT1zfaWyqCtNZQWCLByQyA3JrSQyuYAqnQ=
github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
@@ -492,6 +494,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.2-0.20190702141536-6ffe496ea953 h1:oBvgW8IvwF278gJ3R4hH0gD3ZeJxjwBXVIScRR0dRc8=
github.com/posener/complete v1.2.2-0.20190702141536-6ffe496ea953/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA=

View File

@@ -0,0 +1,98 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// LoginOauth2AuthRequest login oauth2 auth request
//
// swagger:model loginOauth2AuthRequest
type LoginOauth2AuthRequest struct {
// code
// Required: true
Code *string `json:"code"`
// state
// Required: true
State *string `json:"state"`
}
// Validate validates this login oauth2 auth request
func (m *LoginOauth2AuthRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCode(formats); err != nil {
res = append(res, err)
}
if err := m.validateState(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *LoginOauth2AuthRequest) validateCode(formats strfmt.Registry) error {
if err := validate.Required("code", "body", m.Code); err != nil {
return err
}
return nil
}
func (m *LoginOauth2AuthRequest) validateState(formats strfmt.Registry) error {
if err := validate.Required("state", "body", m.State); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *LoginOauth2AuthRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *LoginOauth2AuthRequest) UnmarshalBinary(b []byte) error {
var res LoginOauth2AuthRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

49
pkg/auth/idp.go Normal file
View File

@@ -0,0 +1,49 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 auth
import (
"context"
"github.com/minio/mcs/pkg/auth/idp/oauth2"
)
// IdentityProviderClient interface with all functions to be implemented
// by mock when testing, it should include all IdentityProviderClient respective api calls
// that are used within this project.
type IdentityProviderClient interface {
VerifyIdentity(ctx context.Context, code, state string) (*oauth2.User, error)
GenerateLoginURL() string
}
// Interface implementation
//
// Define the structure of a IdentityProvider Client and define the functions that are actually used
// during the authentication flow.
type IdentityProvider struct {
Client IdentityProviderClient
}
// VerifyIdentity will verify the user identity against the idp using the authorization code flow
func (c IdentityProvider) VerifyIdentity(ctx context.Context, code, state string) (*oauth2.User, error) {
return c.Client.VerifyIdentity(ctx, code, state)
}
// GenerateLoginURL returns a new URL used by the user to login against the idp
func (c IdentityProvider) GenerateLoginURL() string {
return c.Client.GenerateLoginURL()
}

View File

@@ -0,0 +1,71 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 oauth2 contains all the necessary configurations to initialize the
// idp communication using oauth2 protocol
package oauth2
import (
"github.com/minio/mcs/pkg/auth/utils"
"github.com/minio/minio/pkg/env"
)
func GetIdpURL() string {
return env.Get(McsIdpURL, "")
}
func GetIdpClientID() string {
return env.Get(McsIdpClientID, "")
}
func GetIdpSecret() string {
return env.Get(McsIdpSecret, "")
}
// Public endpoint used by the identity oidcProvider when redirecting the user after identity verification
func GetIdpCallbackURL() string {
return env.Get(McsIdpCallbackURL, "")
}
func GetIdpAdminRoles() string {
return env.Get(McsIdpAdminRoles, "")
}
func IsIdpEnabled() bool {
return GetIdpURL() != "" &&
GetIdpClientID() != "" &&
GetIdpSecret() != "" &&
GetIdpCallbackURL() != ""
}
var defaultPassphraseForIdpHmac = utils.RandomCharString(64)
// GetPassphraseForIdpHmac returns passphrase for the pbkdf2 function used to sign the oauth2 state parameter
func getPassphraseForIdpHmac() string {
return env.Get(McsIdpHmacPassphrase, defaultPassphraseForIdpHmac)
}
var defaultSaltForIdpHmac = utils.RandomCharString(64)
// GetSaltForIdpHmac returns salt for the pbkdf2 function used to sign the oauth2 state parameter
func getSaltForIdpHmac() string {
return env.Get(McsIdpHmacSalt, defaultSaltForIdpHmac)
}
// GetSaltForIdpHmac returns the policy to be assigned to the users authenticating via an IDP
func GetIDPPolicyForUser() string {
return env.Get(McsIdpPolicyUser, "mcsAdmin")
}

View File

@@ -0,0 +1,29 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 oauth2
const (
// const for idp configuration
McsIdpURL = "MCS_IDP_URL"
McsIdpClientID = "MCS_IDP_CLIENT_ID"
McsIdpSecret = "MCS_IDP_SECRET"
McsIdpCallbackURL = "MCS_IDP_CALLBACK"
McsIdpAdminRoles = "MCS_IDP_ADMIN_ROLES"
McsIdpHmacPassphrase = "MCS_IDP_HMAC_PASSPHRASE"
McsIdpHmacSalt = "MCS_IDP_HMAC_SALT"
McsIdpPolicyUser = "MCS_IDP_POLICY_USER"
)

View File

@@ -0,0 +1,229 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 oauth2
import (
"context"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"github.com/coreos/go-oidc"
"github.com/minio/mcs/pkg/auth/utils"
"golang.org/x/crypto/pbkdf2"
xoauth2 "golang.org/x/oauth2"
)
var (
errGeneric = errors.New("an error occurred, please try again")
)
type Configuration interface {
Exchange(ctx context.Context, code string, opts ...xoauth2.AuthCodeOption) (*xoauth2.Token, error)
AuthCodeURL(state string, opts ...xoauth2.AuthCodeOption) string
PasswordCredentialsToken(ctx context.Context, username string, password string) (*xoauth2.Token, error)
Client(ctx context.Context, t *xoauth2.Token) *http.Client
TokenSource(ctx context.Context, t *xoauth2.Token) xoauth2.TokenSource
}
type Config struct {
xoauth2.Config
}
func (ac Config) Exchange(ctx context.Context, code string, opts ...xoauth2.AuthCodeOption) (*xoauth2.Token, error) {
return ac.Exchange(ctx, code, opts...)
}
func (ac Config) AuthCodeURL(state string, opts ...xoauth2.AuthCodeOption) string {
return ac.AuthCodeURL(state, opts...)
}
func (ac Config) PasswordCredentialsToken(ctx context.Context, username string, password string) (*xoauth2.Token, error) {
return ac.PasswordCredentialsToken(ctx, username, password)
}
func (ac Config) Client(ctx context.Context, t *xoauth2.Token) *http.Client {
return ac.Client(ctx, t)
}
func (ac Config) TokenSource(ctx context.Context, t *xoauth2.Token) xoauth2.TokenSource {
return ac.TokenSource(ctx, t)
}
// Provider is a wrapper of the oauth2 configuration and the oidc provider
type Provider struct {
// oauth2Config is an interface configuration that contains the following fields
// Config{
// ClientID string
// ClientSecret string
// RedirectURL string
// Endpoint oauth2.Endpoint
// Scopes []string
// }
// - ClientID 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.
// - Endpoint contains the resource server's token endpoint
// URLs. These are constants specific to each server and are
// often available via site-specific packages, such as
// google.Endpoint or github.Endpoint.
// - Scopes specifies optional requested permissions.
ClientID string
oauth2Config Configuration
oidcProvider *oidc.Provider
}
// derivedKey is the key used to compute the HMAC for signing the oauth state parameter
// its derived using pbkdf on MCS_IDP_HMAC_PASSPHRASE with MCS_IDP_HMAC_SALT
var derivedKey = pbkdf2.Key([]byte(getPassphraseForIdpHmac()), []byte(getSaltForIdpHmac()), 4096, 32, sha1.New)
// NewOauth2ProviderClient instantiates a new oauth2 client using the configured credentials
// it returns a *Provider object that contains the necessary configuration to initiate an
// oauth2 authentication flow
func NewOauth2ProviderClient(ctx context.Context, scopes []string) (*Provider, error) {
provider, err := oidc.NewProvider(ctx, GetIdpURL())
if err != nil {
return nil, err
}
// If provided scopes are empty we use a default list
if len(scopes) == 0 {
scopes = []string{oidc.ScopeOpenID, "profile", "app_metadata", "user_metadata", "email"}
}
client := new(Provider)
config := xoauth2.Config{
ClientID: GetIdpClientID(),
ClientSecret: GetIdpSecret(),
RedirectURL: GetIdpCallbackURL(),
Endpoint: provider.Endpoint(),
Scopes: scopes,
}
client.oauth2Config = &config
client.oidcProvider = provider
client.ClientID = GetIdpClientID()
return client, nil
}
type User struct {
AppMetadata map[string]interface{} `json:"app_metadata"`
Blocked bool `json:"blocked"`
CreatedAt string `json:"created_at"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
FamilyName string `json:"family_name"`
GivenName string `json:"given_name"`
Identities []interface{} `json:"identities"`
LastIP string `json:"last_ip"`
LastLogin string `json:"last_login"`
LastPasswordReset string `json:"last_password_reset"`
LoginsCount int `json:"logins_count"`
Mltifactor string `json:"multifactor"`
Name string `json:"name"`
Nickname string `json:"nickname"`
PhoneNumber string `json:"phone_number"`
PhoneVerified bool `json:"phone_verified"`
Picture string `json:"picture"`
UpdatedAt string `json:"updated_at"`
UserID string `json:"user_id"`
UserMetadata map[string]interface{} `json:"user_metadata"`
Username string `json:"username"`
}
// VerifyIdentity will contact the configured IDP and validate the user identity based on the authorization code
func (client *Provider) VerifyIdentity(ctx context.Context, code, state string) (*User, error) {
// verify the provided state is valid (prevents CSRF attacks)
if !validateOauth2State(state) {
return nil, errGeneric
}
// verify the authorization code against the identity oidcProvider
// idp will return a token in exchange
token, err := client.oauth2Config.Exchange(ctx, code)
if err != nil {
log.Println("Failed to verify authorization code", err)
return nil, errGeneric
}
// extract and check id_token field is provided in the response
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
log.Println("No id_token field in oauth2 token")
return nil, errGeneric
}
config := &oidc.Config{
ClientID: client.ClientID,
}
idToken, err := client.oidcProvider.Verifier(config).Verify(ctx, rawIDToken)
if err != nil {
log.Println("Failed to verify ID token", err)
return nil, errGeneric
}
var profile User
// Populate the profile object using the claims included in the token
if err := idToken.Claims(&profile); err != nil {
log.Println("Failed to read profile information", err)
return nil, errGeneric
}
return &profile, nil
}
// validateOauth2State validates the provided state was originated using the same
// instance (or one configured using the same secrets) of MCS, this is basically used to prevent CSRF attacks
// https://security.stackexchange.com/questions/20187/oauth2-cross-site-request-forgery-and-state-parameter
func validateOauth2State(state string) bool {
// state contains a base64 encoded string that may ends with "==", the browser encodes that to "%3D%3D"
// query unescape is need it before trying to decode the base64 string
encodedMessage, err := url.QueryUnescape(state)
if err != nil {
log.Println(err)
return false
}
// decode the state parameter value
message, err := base64.StdEncoding.DecodeString(encodedMessage)
if err != nil {
log.Println(err)
return false
}
s := strings.Split(string(message), ":")
// Validate that the decoded message has the right format "message:hmac"
if len(s) != 2 {
return false
}
// extract the state and hmac
incomingState, incomingHmac := s[0], s[1]
// validate that hmac(incomingState + pbkdf2(secret, salt)) == incomingHmac
return utils.ComputeHmac256(incomingState, derivedKey) == incomingHmac
}
// GetRandomStateWithHMAC computes message + hmac(message, pbkdf2(key, salt)) to be used as state during the oauth authorization
func GetRandomStateWithHMAC(length int) string {
state := utils.RandomCharString(length)
hmac := utils.ComputeHmac256(state, derivedKey)
return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", state, hmac)))
}
// GenerateLoginURL returns a new login URL based on the configured IDP
func (client *Provider) GenerateLoginURL() string {
// generates random state and sign it using HMAC256
state := GetRandomStateWithHMAC(25)
loginURL := client.oauth2Config.AuthCodeURL(state)
return strings.TrimSpace(loginURL)
}

View File

@@ -0,0 +1,98 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 oauth2
import (
"context"
"net/http"
"testing"
"github.com/coreos/go-oidc"
"github.com/stretchr/testify/assert"
"golang.org/x/oauth2"
)
type Oauth2configMock struct{}
var oauth2ConfigExchangeMock func(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error)
var oauth2ConfigAuthCodeURLMock func(state string, opts ...oauth2.AuthCodeOption) string
var oauth2ConfigPasswordCredentialsTokenMock func(ctx context.Context, username string, password string) (*oauth2.Token, error)
var oauth2ConfigClientMock func(ctx context.Context, t *oauth2.Token) *http.Client
var oauth2ConfigokenSourceMock func(ctx context.Context, t *oauth2.Token) oauth2.TokenSource
func (ac Oauth2configMock) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
return oauth2ConfigExchangeMock(ctx, code, opts...)
}
func (ac Oauth2configMock) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
return oauth2ConfigAuthCodeURLMock(state, opts...)
}
func (ac Oauth2configMock) PasswordCredentialsToken(ctx context.Context, username string, password string) (*oauth2.Token, error) {
return oauth2ConfigPasswordCredentialsTokenMock(ctx, username, password)
}
func (ac Oauth2configMock) Client(ctx context.Context, t *oauth2.Token) *http.Client {
return oauth2ConfigClientMock(ctx, t)
}
func (ac Oauth2configMock) TokenSource(ctx context.Context, t *oauth2.Token) oauth2.TokenSource {
return oauth2ConfigokenSourceMock(ctx, t)
}
func TestGenerateLoginURL(t *testing.T) {
funcAssert := assert.New(t)
oauth2Provider := Provider{
oauth2Config: Oauth2configMock{},
oidcProvider: &oidc.Provider{},
}
// Test-1 : GenerateLoginURL() generates URL correctly with provided state
oauth2ConfigAuthCodeURLMock = func(state string, opts ...oauth2.AuthCodeOption) string {
// Internally we are testing the private method getRandomStateWithHMAC, this function should always returns
// a non-empty string
return state
}
url := oauth2Provider.GenerateLoginURL()
funcAssert.NotEqual("", url)
}
func TestVerifyIdentity(t *testing.T) {
ctx := context.Background()
funcAssert := assert.New(t)
// mock data
oauth2Provider := Provider{
oauth2Config: Oauth2configMock{},
oidcProvider: &oidc.Provider{},
}
// Test-1 : VerifyIdentity() should fail because of bad state token
_, err := oauth2Provider.VerifyIdentity(ctx, "AAABBBCCCDDDEEEFFF", "badtoken")
funcAssert.NotNil(err)
// Test-2 : VerifyIdentity() should fail because no id_token is provided by the idp
oauth2ConfigExchangeMock = func(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
return &oauth2.Token{}, nil
}
state := GetRandomStateWithHMAC(32)
code := "AAABBBCCCDDDEEEFFF"
_, err = oauth2Provider.VerifyIdentity(ctx, code, state)
funcAssert.NotNil(err)
// Test-3 : VerifyIdentity() should fail because no id_token is provided by the idp
// TODO
// Test-4 : VerifyIdentity() should fail because oidcProvider.Verifier returned an error
// TODO
// Test-5 : VerifyIdentity() should fail because idToken.Claims contains invalid fields
// TODO
}

View File

@@ -17,45 +17,15 @@
package jwt
import (
"crypto/rand"
"io"
"strconv"
"strings"
"time"
"github.com/minio/mcs/pkg/auth/utils"
"github.com/minio/minio/pkg/env"
)
// Do not use:
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
// It relies on math/rand and therefore not on a cryptographically secure RNG => It must not be used
// for access/secret keys.
// The alphabet of random character string. Each character must be unique.
//
// The RandomCharString implementation requires that: 256 / len(letters) is a natural numbers.
// For example: 256 / 64 = 4. However, 5 > 256/62 > 4 and therefore we must not use a alphabet
// of 62 characters.
// The reason is that if 256 / len(letters) is not a natural number then certain characters become
// more likely then others.
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
func RandomCharString(n int) string {
random := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, random); err != nil {
panic(err) // Can only happen if we would run out of entropy.
}
var s strings.Builder
for _, v := range random {
j := v % byte(len(letters))
s.WriteByte(letters[j])
}
return s.String()
}
// defaultHmacJWTPassphrase will be used by default if application is not configured with a custom MCS_HMAC_JWT_SECRET secret
var defaultHmacJWTPassphrase = RandomCharString(64)
var defaultHmacJWTPassphrase = utils.RandomCharString(64)
// GetHmacJWTSecret returns the 64 bytes secret used for signing the generated JWT for the application
func GetHmacJWTSecret() string {
@@ -78,15 +48,14 @@ func GetMcsSTSAndJWTDurationTime() time.Duration {
return time.Duration(duration) * time.Second
}
// defaultPBKDFPassphrase
var defaultPBKDFPassphrase = RandomCharString(64)
var defaultPBKDFPassphrase = utils.RandomCharString(64)
// GetPBKDFPassphrase returns passphrase for the pbkdf2 function used to encrypt JWT payload
func GetPBKDFPassphrase() string {
return env.Get(McsPBKDFPassphrase, defaultPBKDFPassphrase)
}
var defaultPBKDFSalt = RandomCharString(64)
var defaultPBKDFSalt = utils.RandomCharString(64)
// GetPBKDFSalt returns salt for the pbkdf2 function used to encrypt JWT payload
func GetPBKDFSalt() string {

60
pkg/auth/utils/utils.go Normal file
View File

@@ -0,0 +1,60 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 utils
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"io"
"strings"
)
// Do not use:
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
// It relies on math/rand and therefore not on a cryptographically secure RNG => It must not be used
// for access/secret keys.
// The alphabet of random character string. Each character must be unique.
//
// The RandomCharString implementation requires that: 256 / len(letters) is a natural numbers.
// For example: 256 / 64 = 4. However, 5 > 256/62 > 4 and therefore we must not use a alphabet
// of 62 characters.
// The reason is that if 256 / len(letters) is not a natural number then certain characters become
// more likely then others.
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
func RandomCharString(n int) string {
random := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, random); err != nil {
panic(err) // Can only happen if we would run out of entropy.
}
var s strings.Builder
for _, v := range random {
j := v % byte(len(letters))
s.WriteByte(letters[j])
}
return s.String()
}
func ComputeHmac256(message string, key []byte) string {
h := hmac.New(sha256.New, key)
h.Write([]byte(message))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

View File

@@ -0,0 +1,46 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 utils
import (
"crypto/sha1"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/pbkdf2"
)
func TestRandomCharString(t *testing.T) {
funcAssert := assert.New(t)
// Test-1 : RandomCharString() should return string with expected length
length := 32
token := RandomCharString(length)
funcAssert.Equal(length, len(token))
// Test-2 : RandomCharString() should output random string, new generated string should not be equal to the previous one
newToken := RandomCharString(length)
funcAssert.NotEqual(token, newToken)
}
func TestComputeHmac256(t *testing.T) {
funcAssert := assert.New(t)
// Test-1 : ComputeHmac256() should return the right Hmac256 string based on a derived key
var derivedKey = pbkdf2.Key([]byte("secret"), []byte("salt"), 4096, 32, sha1.New)
var message = "hello world"
var expectedHmac = "5r32q7W+0hcBnqzQwJJUDzVGoVivXGSodTcHSqG/9Q8="
hmac := ComputeHmac256(message, derivedKey)
funcAssert.Equal(hmac, expectedHmac)
}

View File

@@ -62,7 +62,7 @@
"last 1 safari version"
]
},
"proxy": "http://localhost:9090",
"proxy": "http://localhost:9090/",
"devDependencies": {
"prettier": "^1.19.1"
}

View File

@@ -17,13 +17,14 @@
import React from "react";
import { Redirect, Route, Router, Switch } from "react-router-dom";
import history from "./history";
import Login from "./screens/LoginPage";
import Login from "./screens/LoginPage/LoginPage";
import Console from "./screens/Console/Console";
import NotFoundPage from "./screens/NotFoundPage";
import storage from "local-storage-fallback";
import { connect } from "react-redux";
import { AppState } from "./store";
import { userLoggedIn } from "./actions";
import LoginCallback from "./screens/LoginPage/LoginCallback";
const isLoggedIn = () => {
return (
@@ -55,6 +56,7 @@ class Routes extends React.Component<RoutesProps> {
return (
<Router history={history}>
<Switch>
<Route exact path="/oauth_callback" component={LoginCallback} />
<Route exact path="/login" component={Login} />
{this.props.loggedIn ? (
<Switch>

View File

@@ -0,0 +1,43 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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/>.
import React, {FC, useEffect} from "react";
import {RouteComponentProps} from "react-router";
import storage from "local-storage-fallback";
import api from "../../common/api";
const LoginCallback: FC<RouteComponentProps> = ({location}) => {
useEffect(() => {
const code = (location.search.match(/code=([^&]+)/) || [])[1];
const state = (location.search.match(/state=([^&]+)/) || [])[1];
api
.invoke("POST", "/api/v1/login/oauth2/auth", {code, state})
.then((res: any) => {
if (res && res.sessionId) {
// store the jwt token
storage.setItem("token", res.sessionId);
// We push to history the new URL.
window.location.href = "/dashboard";
}
})
.catch((res: any) => {
window.location.href = "/login";
});
}, []);
return null;
};
export default LoginCallback;

View File

@@ -22,18 +22,20 @@ import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { Paper } from "@material-ui/core";
import { CircularProgress, Paper } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { SystemState } from "../types";
import { userLoggedIn } from "../actions";
import history from "../history";
import { SystemState } from "../../types";
import { userLoggedIn } from "../../actions";
import history from "../../history";
import api from "../../common/api";
import { ILoginDetails } from "./types";
const styles = (theme: Theme) =>
createStyles({
"@global": {
body: {
backgroundColor: "#F4F4F4"
}
backgroundColor: "#F4F4F4",
},
},
paper: {
marginTop: theme.spacing(16),
@@ -42,45 +44,48 @@ const styles = (theme: Theme) =>
flexDirection: "column",
alignItems: "center",
width: "800px",
margin: "auto"
margin: "auto",
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main
backgroundColor: theme.palette.secondary.main,
},
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(3)
marginTop: theme.spacing(3),
},
submit: {
margin: theme.spacing(3, 0, 2)
margin: theme.spacing(3, 0, 2),
},
errorBlock: {
color: "red"
color: "red",
},
mainContainer: {
borderRadius: "3px"
borderRadius: "3px",
},
theOcean: {
borderTopLeftRadius: "3px",
borderBottomLeftRadius: "3px",
background:
"transparent linear-gradient(333deg, #281B6F 1%, #271260 13%, #120D53 83%) 0% 0% no-repeat padding-box;"
"transparent linear-gradient(333deg, #281B6F 1%, #271260 13%, #120D53 83%) 0% 0% no-repeat padding-box;",
},
oceanBg: {
backgroundImage: "url(/images/BG_Illustration.svg)",
backgroundRepeat: "no-repeat",
backgroundPosition: "bottom left",
height: "100%",
width: "100%"
width: "100%",
},
theLogin: {
padding: "76px 62px 20px 62px"
}
padding: "76px 62px 20px 62px",
},
loadingLoginStrategy: {
textAlign: "center",
},
});
const mapState = (state: SystemState) => ({
loggedIn: state.loggedIn
loggedIn: state.loggedIn,
});
const connector = connect(mapState, { userLoggedIn });
@@ -90,18 +95,51 @@ const connector = connect(mapState, { userLoggedIn });
type PropsFromRedux = ConnectedProps<typeof connector>;
type Props = PropsFromRedux & {};
interface LoginProps {
interface ILoginProps {
userLoggedIn: typeof userLoggedIn;
classes: any;
}
class Login extends React.Component<LoginProps> {
state = {
interface ILoginState {
accessKey: string;
secretKey: string;
error: string;
loading: boolean;
loginStrategy: ILoginDetails;
}
class Login extends React.Component<ILoginProps, ILoginState> {
state: ILoginState = {
accessKey: "",
secretKey: "",
error: ""
error: "",
loading: false,
loginStrategy: {
loginStrategy: "",
redirect: "",
},
};
fetchConfiguration() {
this.setState({ loading: true }, () => {
api
.invoke("GET", "/api/v1/login")
.then((loginDetails: ILoginDetails) => {
this.setState({
loading: false,
});
this.setState({
loading: false,
loginStrategy: loginDetails,
error: "",
});
})
.catch((err: any) => {
this.setState({ loading: false, error: err });
});
});
}
formSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const url = "/api/v1/login";
@@ -128,21 +166,25 @@ class Login extends React.Component<LoginProps> {
// We push to history the new URL.
history.push("/dashboard");
})
.catch(err => {
.catch((err) => {
this.setState({ error: `${err}` });
});
};
componentDidMount(): void {
this.fetchConfiguration();
}
render() {
const { error, accessKey, secretKey } = this.state;
const { error, accessKey, secretKey, loginStrategy } = this.state;
const { classes } = this.props;
return (
<Paper className={classes.paper}>
<Grid container className={classes.mainContainer}>
<Grid item xs={7} className={classes.theOcean}>
<div className={classes.oceanBg}></div>
</Grid>
<Grid item xs={5} className={classes.theLogin}>
let loginComponent = null;
switch (loginStrategy.loginStrategy) {
case "form": {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
Login
</Typography>
@@ -203,6 +245,45 @@ class Login extends React.Component<LoginProps> {
Login
</Button>
</form>
</React.Fragment>
);
break;
}
case "redirect": {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
Login
</Typography>
<Button
component={"a"}
href={loginStrategy.redirect}
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Welcome
</Button>
</React.Fragment>
);
break;
}
default:
loginComponent = (
<CircularProgress className={classes.loadingLoginStrategy} />
);
}
return (
<Paper className={classes.paper}>
<Grid container className={classes.mainContainer}>
<Grid item xs={7} className={classes.theOcean}>
<div className={classes.oceanBg} />
</Grid>
<Grid item xs={5} className={classes.theLogin}>
{loginComponent}
</Grid>
</Grid>
</Paper>

View File

@@ -0,0 +1,20 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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/>.
export interface ILoginDetails {
loginStrategy: string;
redirect: string;
}

View File

@@ -119,9 +119,6 @@ type MCSCredentials interface {
}
// Interface implementation
//
// Define the structure of a mc S3Client and define the functions that are actually used
// from mcsCredentials api.
type mcsCredentials struct {
minioCredentials *credentials.Credentials
}

View File

@@ -17,6 +17,7 @@
package restapi
const (
// consts for common configuration
McsVersion = `0.1.0`
McsAccessKey = "MCS_ACCESS_KEY"
McsSecretKey = "MCS_SECRET_KEY"
@@ -27,6 +28,7 @@ const (
McsTLSHostname = "MCS_TLS_HOSTNAME"
McsTLSPort = "MCS_TLS_PORT"
// consts for Secure middleware
McsSecureAllowedHosts = "MCS_SECURE_ALLOWED_HOSTS"
McsSecureAllowedHostsAreRegex = "MCS_SECURE_ALLOWED_HOSTS_ARE_REGEX"
McsSecureFrameDeny = "MCS_SECURE_FRAME_DENY"

View File

@@ -754,6 +754,40 @@ func init() {
}
}
},
"/login/oauth2/auth": {
"post": {
"security": [],
"tags": [
"UserAPI"
],
"summary": "Identity Provider oauth2 callback endpoint.",
"operationId": "LoginOauth2Auth",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/loginOauth2AuthRequest"
}
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/logout": {
"post": {
"tags": [
@@ -1630,6 +1664,21 @@ func init() {
}
}
},
"loginOauth2AuthRequest": {
"type": "object",
"required": [
"state",
"code"
],
"properties": {
"code": {
"type": "string"
},
"state": {
"type": "string"
}
}
},
"loginRequest": {
"type": "object",
"required": [
@@ -2745,6 +2794,40 @@ func init() {
}
}
},
"/login/oauth2/auth": {
"post": {
"security": [],
"tags": [
"UserAPI"
],
"summary": "Identity Provider oauth2 callback endpoint.",
"operationId": "LoginOauth2Auth",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/loginOauth2AuthRequest"
}
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/logout": {
"post": {
"tags": [
@@ -3621,6 +3704,21 @@ func init() {
}
}
},
"loginOauth2AuthRequest": {
"type": "object",
"required": [
"state",
"code"
],
"properties": {
"code": {
"type": "string"
},
"state": {
"type": "string"
}
}
},
"loginRequest": {
"type": "object",
"required": [

View File

@@ -135,6 +135,9 @@ func NewMcsAPI(spec *loads.Document) *McsAPI {
UserAPILoginDetailHandler: user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginDetail has not yet been implemented")
}),
UserAPILoginOauth2AuthHandler: user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginOauth2Auth has not yet been implemented")
}),
UserAPILogoutHandler: user_api.LogoutHandlerFunc(func(params user_api.LogoutParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.Logout has not yet been implemented")
}),
@@ -280,6 +283,8 @@ type McsAPI struct {
UserAPILoginHandler user_api.LoginHandler
// UserAPILoginDetailHandler sets the operation handler for the login detail operation
UserAPILoginDetailHandler user_api.LoginDetailHandler
// UserAPILoginOauth2AuthHandler sets the operation handler for the login oauth2 auth operation
UserAPILoginOauth2AuthHandler user_api.LoginOauth2AuthHandler
// UserAPILogoutHandler sets the operation handler for the logout operation
UserAPILogoutHandler user_api.LogoutHandler
// UserAPIMakeBucketHandler sets the operation handler for the make bucket operation
@@ -457,6 +462,9 @@ func (o *McsAPI) Validate() error {
if o.UserAPILoginDetailHandler == nil {
unregistered = append(unregistered, "user_api.LoginDetailHandler")
}
if o.UserAPILoginOauth2AuthHandler == nil {
unregistered = append(unregistered, "user_api.LoginOauth2AuthHandler")
}
if o.UserAPILogoutHandler == nil {
unregistered = append(unregistered, "user_api.LogoutHandler")
}
@@ -704,6 +712,10 @@ func (o *McsAPI) initHandlerCache() {
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login/oauth2/auth"] = user_api.NewLoginOauth2Auth(o.context, o.UserAPILoginOauth2AuthHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/logout"] = user_api.NewLogout(o.context, o.UserAPILogoutHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)

View File

@@ -0,0 +1,75 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// LoginOauth2AuthHandlerFunc turns a function with the right signature into a login oauth2 auth handler
type LoginOauth2AuthHandlerFunc func(LoginOauth2AuthParams) middleware.Responder
// Handle executing the request and returning a response
func (fn LoginOauth2AuthHandlerFunc) Handle(params LoginOauth2AuthParams) middleware.Responder {
return fn(params)
}
// LoginOauth2AuthHandler interface for that can handle valid login oauth2 auth params
type LoginOauth2AuthHandler interface {
Handle(LoginOauth2AuthParams) middleware.Responder
}
// NewLoginOauth2Auth creates a new http.Handler for the login oauth2 auth operation
func NewLoginOauth2Auth(ctx *middleware.Context, handler LoginOauth2AuthHandler) *LoginOauth2Auth {
return &LoginOauth2Auth{Context: ctx, Handler: handler}
}
/*LoginOauth2Auth swagger:route POST /login/oauth2/auth UserAPI loginOauth2Auth
Identity Provider oauth2 callback endpoint.
*/
type LoginOauth2Auth struct {
Context *middleware.Context
Handler LoginOauth2AuthHandler
}
func (o *LoginOauth2Auth) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewLoginOauth2AuthParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,94 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/mcs/models"
)
// NewLoginOauth2AuthParams creates a new LoginOauth2AuthParams object
// no default values defined in spec.
func NewLoginOauth2AuthParams() LoginOauth2AuthParams {
return LoginOauth2AuthParams{}
}
// LoginOauth2AuthParams contains all the bound params for the login oauth2 auth operation
// typically these are obtained from a http.Request
//
// swagger:parameters LoginOauth2Auth
type LoginOauth2AuthParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.LoginOauth2AuthRequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewLoginOauth2AuthParams() beforehand.
func (o *LoginOauth2AuthParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.LoginOauth2AuthRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body"))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body"))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/mcs/models"
)
// LoginOauth2AuthCreatedCode is the HTTP code returned for type LoginOauth2AuthCreated
const LoginOauth2AuthCreatedCode int = 201
/*LoginOauth2AuthCreated A successful login.
swagger:response loginOauth2AuthCreated
*/
type LoginOauth2AuthCreated struct {
/*
In: Body
*/
Payload *models.LoginResponse `json:"body,omitempty"`
}
// NewLoginOauth2AuthCreated creates LoginOauth2AuthCreated with default headers values
func NewLoginOauth2AuthCreated() *LoginOauth2AuthCreated {
return &LoginOauth2AuthCreated{}
}
// WithPayload adds the payload to the login oauth2 auth created response
func (o *LoginOauth2AuthCreated) WithPayload(payload *models.LoginResponse) *LoginOauth2AuthCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login oauth2 auth created response
func (o *LoginOauth2AuthCreated) SetPayload(payload *models.LoginResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginOauth2AuthCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*LoginOauth2AuthDefault Generic error response.
swagger:response loginOauth2AuthDefault
*/
type LoginOauth2AuthDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewLoginOauth2AuthDefault creates LoginOauth2AuthDefault with default headers values
func NewLoginOauth2AuthDefault(code int) *LoginOauth2AuthDefault {
if code <= 0 {
code = 500
}
return &LoginOauth2AuthDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the login oauth2 auth default response
func (o *LoginOauth2AuthDefault) WithStatusCode(code int) *LoginOauth2AuthDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the login oauth2 auth default response
func (o *LoginOauth2AuthDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the login oauth2 auth default response
func (o *LoginOauth2AuthDefault) WithPayload(payload *models.Error) *LoginOauth2AuthDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login oauth2 auth default response
func (o *LoginOauth2AuthDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginOauth2AuthDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// LoginOauth2AuthURL generates an URL for the login oauth2 auth operation
type LoginOauth2AuthURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginOauth2AuthURL) WithBasePath(bp string) *LoginOauth2AuthURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginOauth2AuthURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *LoginOauth2AuthURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/login/oauth2/auth"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *LoginOauth2AuthURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *LoginOauth2AuthURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *LoginOauth2AuthURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on LoginOauth2AuthURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on LoginOauth2AuthURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *LoginOauth2AuthURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,75 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// LoginOauth2CallbackHandlerFunc turns a function with the right signature into a login oauth2 callback handler
type LoginOauth2CallbackHandlerFunc func(LoginOauth2CallbackParams) middleware.Responder
// Handle executing the request and returning a response
func (fn LoginOauth2CallbackHandlerFunc) Handle(params LoginOauth2CallbackParams) middleware.Responder {
return fn(params)
}
// LoginOauth2CallbackHandler interface for that can handle valid login oauth2 callback params
type LoginOauth2CallbackHandler interface {
Handle(LoginOauth2CallbackParams) middleware.Responder
}
// NewLoginOauth2Callback creates a new http.Handler for the login oauth2 callback operation
func NewLoginOauth2Callback(ctx *middleware.Context, handler LoginOauth2CallbackHandler) *LoginOauth2Callback {
return &LoginOauth2Callback{Context: ctx, Handler: handler}
}
/*LoginOauth2Callback swagger:route GET /login/oauth2/callback UserAPI loginOauth2Callback
Identity Provider oauth2 callback endpoint.
*/
type LoginOauth2Callback struct {
Context *middleware.Context
Handler LoginOauth2CallbackHandler
}
func (o *LoginOauth2Callback) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewLoginOauth2CallbackParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,62 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
)
// NewLoginOauth2CallbackParams creates a new LoginOauth2CallbackParams object
// no default values defined in spec.
func NewLoginOauth2CallbackParams() LoginOauth2CallbackParams {
return LoginOauth2CallbackParams{}
}
// LoginOauth2CallbackParams contains all the bound params for the login oauth2 callback operation
// typically these are obtained from a http.Request
//
// swagger:parameters LoginOauth2Callback
type LoginOauth2CallbackParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewLoginOauth2CallbackParams() beforehand.
func (o *LoginOauth2CallbackParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,113 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/mcs/models"
)
// LoginOauth2CallbackOKCode is the HTTP code returned for type LoginOauth2CallbackOK
const LoginOauth2CallbackOKCode int = 200
/*LoginOauth2CallbackOK A successful response.
swagger:response loginOauth2CallbackOK
*/
type LoginOauth2CallbackOK struct {
}
// NewLoginOauth2CallbackOK creates LoginOauth2CallbackOK with default headers values
func NewLoginOauth2CallbackOK() *LoginOauth2CallbackOK {
return &LoginOauth2CallbackOK{}
}
// WriteResponse to the client
func (o *LoginOauth2CallbackOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(200)
}
/*LoginOauth2CallbackDefault Generic error response.
swagger:response loginOauth2CallbackDefault
*/
type LoginOauth2CallbackDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewLoginOauth2CallbackDefault creates LoginOauth2CallbackDefault with default headers values
func NewLoginOauth2CallbackDefault(code int) *LoginOauth2CallbackDefault {
if code <= 0 {
code = 500
}
return &LoginOauth2CallbackDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the login oauth2 callback default response
func (o *LoginOauth2CallbackDefault) WithStatusCode(code int) *LoginOauth2CallbackDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the login oauth2 callback default response
func (o *LoginOauth2CallbackDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the login oauth2 callback default response
func (o *LoginOauth2CallbackDefault) WithPayload(payload *models.Error) *LoginOauth2CallbackDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login oauth2 callback default response
func (o *LoginOauth2CallbackDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginOauth2CallbackDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// LoginOauth2CallbackURL generates an URL for the login oauth2 callback operation
type LoginOauth2CallbackURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginOauth2CallbackURL) WithBasePath(bp string) *LoginOauth2CallbackURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginOauth2CallbackURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *LoginOauth2CallbackURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/login/oauth2/callback"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *LoginOauth2CallbackURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *LoginOauth2CallbackURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *LoginOauth2CallbackURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on LoginOauth2CallbackURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on LoginOauth2CallbackURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *LoginOauth2CallbackURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -0,0 +1,75 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// Oauth2CallbackHandlerFunc turns a function with the right signature into a oauth2 callback handler
type Oauth2CallbackHandlerFunc func(Oauth2CallbackParams) middleware.Responder
// Handle executing the request and returning a response
func (fn Oauth2CallbackHandlerFunc) Handle(params Oauth2CallbackParams) middleware.Responder {
return fn(params)
}
// Oauth2CallbackHandler interface for that can handle valid oauth2 callback params
type Oauth2CallbackHandler interface {
Handle(Oauth2CallbackParams) middleware.Responder
}
// NewOauth2Callback creates a new http.Handler for the oauth2 callback operation
func NewOauth2Callback(ctx *middleware.Context, handler Oauth2CallbackHandler) *Oauth2Callback {
return &Oauth2Callback{Context: ctx, Handler: handler}
}
/*Oauth2Callback swagger:route GET /login/oauth2/callback UserAPI oauth2Callback
Identity Provider oauth2 callback endpoint.
*/
type Oauth2Callback struct {
Context *middleware.Context
Handler Oauth2CallbackHandler
}
func (o *Oauth2Callback) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewOauth2CallbackParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,62 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
)
// NewOauth2CallbackParams creates a new Oauth2CallbackParams object
// no default values defined in spec.
func NewOauth2CallbackParams() Oauth2CallbackParams {
return Oauth2CallbackParams{}
}
// Oauth2CallbackParams contains all the bound params for the oauth2 callback operation
// typically these are obtained from a http.Request
//
// swagger:parameters Oauth2Callback
type Oauth2CallbackParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewOauth2CallbackParams() beforehand.
func (o *Oauth2CallbackParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,113 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/mcs/models"
)
// Oauth2CallbackOKCode is the HTTP code returned for type Oauth2CallbackOK
const Oauth2CallbackOKCode int = 200
/*Oauth2CallbackOK A successful response.
swagger:response oauth2CallbackOK
*/
type Oauth2CallbackOK struct {
}
// NewOauth2CallbackOK creates Oauth2CallbackOK with default headers values
func NewOauth2CallbackOK() *Oauth2CallbackOK {
return &Oauth2CallbackOK{}
}
// WriteResponse to the client
func (o *Oauth2CallbackOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(200)
}
/*Oauth2CallbackDefault Generic error response.
swagger:response oauth2CallbackDefault
*/
type Oauth2CallbackDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewOauth2CallbackDefault creates Oauth2CallbackDefault with default headers values
func NewOauth2CallbackDefault(code int) *Oauth2CallbackDefault {
if code <= 0 {
code = 500
}
return &Oauth2CallbackDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the oauth2 callback default response
func (o *Oauth2CallbackDefault) WithStatusCode(code int) *Oauth2CallbackDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the oauth2 callback default response
func (o *Oauth2CallbackDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the oauth2 callback default response
func (o *Oauth2CallbackDefault) WithPayload(payload *models.Error) *Oauth2CallbackDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the oauth2 callback default response
func (o *Oauth2CallbackDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *Oauth2CallbackDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// Oauth2CallbackURL generates an URL for the oauth2 callback operation
type Oauth2CallbackURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *Oauth2CallbackURL) WithBasePath(bp string) *Oauth2CallbackURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *Oauth2CallbackURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *Oauth2CallbackURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/login/oauth2/callback"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *Oauth2CallbackURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *Oauth2CallbackURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *Oauth2CallbackURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on Oauth2CallbackURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on Oauth2CallbackURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *Oauth2CallbackURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -17,6 +17,7 @@
package restapi
import (
"context"
"errors"
"log"
@@ -24,6 +25,8 @@ import (
"github.com/go-openapi/swag"
"github.com/minio/mcs/models"
"github.com/minio/mcs/pkg/auth"
"github.com/minio/mcs/pkg/auth/idp/oauth2"
"github.com/minio/mcs/pkg/auth/utils"
"github.com/minio/mcs/restapi/operations"
"github.com/minio/mcs/restapi/operations/user_api"
)
@@ -31,7 +34,10 @@ import (
func registerLoginHandlers(api *operations.McsAPI) {
// get login strategy
api.UserAPILoginDetailHandler = user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
loginDetails := getLoginDetailsResponse()
loginDetails, err := getLoginDetailsResponse()
if err != nil {
return user_api.NewLoginDetailDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return user_api.NewLoginDetailOK().WithPayload(loginDetails)
})
// post login
@@ -42,6 +48,13 @@ func registerLoginHandlers(api *operations.McsAPI) {
}
return user_api.NewLoginCreated().WithPayload(loginResponse)
})
api.UserAPILoginOauth2AuthHandler = user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params.Body)
if err != nil {
return user_api.NewLoginOauth2AuthDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return user_api.NewLoginOauth2AuthCreated().WithPayload(loginResponse)
})
}
var errInvalidCredentials = errors.New("invalid minioCredentials")
@@ -81,12 +94,95 @@ func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, error) {
return loginResponse, nil
}
// getLoginDetailsResponse returns wether an IDP is configured or not.
func getLoginDetailsResponse() *models.LoginDetails {
// TODO: Add support for login using external IDPs
// serialize output
loginDetails := &models.LoginDetails{
LoginStrategy: models.LoginDetailsLoginStrategyForm,
// getLoginDetailsResponse returns information regarding the MCS authentication mechanism.
func getLoginDetailsResponse() (*models.LoginDetails, error) {
ctx := context.Background()
loginStrategy := models.LoginDetailsLoginStrategyForm
redirectURL := ""
if oauth2.IsIdpEnabled() {
loginStrategy = models.LoginDetailsLoginStrategyRedirect
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil)
if err != nil {
return nil, err
}
// Validate user against IDP
identityProvider := &auth.IdentityProvider{Client: oauth2Client}
redirectURL = identityProvider.GenerateLoginURL()
}
return loginDetails
loginDetails := &models.LoginDetails{
LoginStrategy: loginStrategy,
Redirect: redirectURL,
}
return loginDetails, nil
}
func loginOauth2Auth(ctx context.Context, provider *auth.IdentityProvider, code, state string) (*oauth2.User, error) {
userIdentity, err := provider.VerifyIdentity(ctx, code, state)
if err != nil {
return nil, err
}
return userIdentity, nil
}
func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.LoginResponse, error) {
ctx := context.Background()
if oauth2.IsIdpEnabled() {
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil)
if err != nil {
return nil, err
}
// initialize new identity provider
identityProvider := &auth.IdentityProvider{Client: oauth2Client}
// Validate user against IDP
identity, err := loginOauth2Auth(ctx, identityProvider, *lr.Code, *lr.State)
if err != nil {
return nil, err
}
mAdmin, err := newSuperMAdminClient()
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
}
adminClient := adminClient{client: mAdmin}
accessKey := identity.Email
secretKey := utils.RandomCharString(32)
// Create user in MinIO
if _, err := addUser(ctx, adminClient, &accessKey, &secretKey, []string{}); err != nil {
log.Println("error adding user:", err)
return nil, err
}
// rollback user if there's an error after this point
defer func() {
if err != nil {
if errRemove := removeUser(ctx, adminClient, accessKey); errRemove != nil {
log.Println("error removing user:", errRemove)
}
}
}()
// assign the "mcsAdmin" policy to this user
if err := setPolicy(ctx, adminClient, oauth2.GetIDPPolicyForUser(), accessKey, models.PolicyEntityUser); err != nil {
log.Println("error setting policy:", err)
return nil, err
}
// User was created correctly, create a new session/JWT
creds, err := newMcsCredentials(accessKey, secretKey, "")
if err != nil {
log.Println("error login:", err)
return nil, err
}
credentials := mcsCredentials{minioCredentials: creds}
jwt, err := login(credentials)
if err != nil {
log.Println("error login:", err)
return nil, err
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *jwt,
}
return loginResponse, nil
}
return nil, errors.New("an error occurred, please try again")
}

View File

@@ -17,9 +17,12 @@
package restapi
import (
"context"
"errors"
"testing"
"github.com/minio/mcs/pkg/auth"
"github.com/minio/mcs/pkg/auth/idp/oauth2"
"github.com/minio/minio-go/v6/pkg/credentials"
"github.com/stretchr/testify/assert"
)
@@ -58,3 +61,43 @@ func TestLogin(t *testing.T) {
_, err = login(mcsCredentials)
funcAssert.NotNil(err, "not error returned creating a session")
}
type IdentityProviderClientMock struct{}
var idpVerifyIdentityMock func(ctx context.Context, code, state string) (*oauth2.User, error)
var idpGenerateLoginURLMock func() string
func (ac IdentityProviderClientMock) VerifyIdentity(ctx context.Context, code, state string) (*oauth2.User, error) {
return idpVerifyIdentityMock(ctx, code, state)
}
func (ac IdentityProviderClientMock) GenerateLoginURL() string {
return idpGenerateLoginURLMock()
}
// TestLoginOauth2Auth is the main function that test the Oauth2 Authentication
func TestLoginOauth2Auth(t *testing.T) {
ctx := context.Background()
funcAssert := assert.New(t)
// mock data
mockCode := "EAEAEAE"
mockState := "HUEHUEHUE"
idpClientMock := IdentityProviderClientMock{}
identityProvider := &auth.IdentityProvider{Client: idpClientMock}
// Test-1 : loginOauth2Auth() correctly authenticates the user
idpVerifyIdentityMock = func(ctx context.Context, code, state string) (*oauth2.User, error) {
return &oauth2.User{}, nil
}
function := "loginOauth2Auth()"
_, err := loginOauth2Auth(ctx, identityProvider, mockCode, mockState)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// Test-2 : loginOauth2Auth() returns an error
idpVerifyIdentityMock = func(ctx context.Context, code, state string) (*oauth2.User, error) {
return nil, errors.New("error")
}
if _, err := loginOauth2Auth(ctx, identityProvider, mockCode, mockState); funcAssert.Error(err) {
funcAssert.Equal("error", err.Error())
}
}

View File

@@ -61,6 +61,29 @@ paths:
tags:
- UserAPI
/login/oauth2/auth:
post:
summary: Identity Provider oauth2 callback endpoint.
operationId: LoginOauth2Auth
parameters:
- name: body
in: body
required: true
schema:
$ref: '#/definitions/loginOauth2AuthRequest'
responses:
201:
description: A successful login.
schema:
$ref: '#/definitions/loginResponse'
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
security: []
tags:
- UserAPI
/logout:
post:
summary: Logout from mcs.
@@ -761,7 +784,6 @@ paths:
$ref: "#/definitions/error"
tags:
- AdminAPI
/profiling/start:
post:
summary: Start recording profile data
@@ -1179,6 +1201,16 @@ definitions:
enum: [form,redirect]
redirect:
type: string
loginOauth2AuthRequest:
type: object
required:
- state
- code
properties:
state:
type: string
code:
type: string
loginRequest:
type: object
required: