OP#792 initial commit after fork
This commit is contained in:
186
internal/config/identity/openid/provider/keycloak.go
Normal file
186
internal/config/identity/openid/provider/keycloak.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// 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 provider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Token - parses the output from IDP id_token.
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
Expiry int `json:"expires_in"`
|
||||
}
|
||||
|
||||
// KeycloakProvider implements Provider interface for KeyCloak Identity Provider.
|
||||
type KeycloakProvider struct {
|
||||
sync.Mutex
|
||||
|
||||
oeConfig DiscoveryDoc
|
||||
client http.Client
|
||||
adminURL string
|
||||
realm string
|
||||
|
||||
// internal value refreshed
|
||||
accessToken Token
|
||||
}
|
||||
|
||||
// LoginWithUser authenticates username/password, not needed for Keycloak
|
||||
func (k *KeycloakProvider) LoginWithUser(username, password string) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// LoginWithClientID is implemented by Keycloak service account support
|
||||
func (k *KeycloakProvider) LoginWithClientID(clientID, clientSecret string) error {
|
||||
values := url.Values{}
|
||||
values.Set("client_id", clientID)
|
||||
values.Set("client_secret", clientSecret)
|
||||
values.Set("grant_type", "client_credentials")
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, k.oeConfig.TokenEndpoint, strings.NewReader(values.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := k.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var accessToken Token
|
||||
if err = json.NewDecoder(resp.Body).Decode(&accessToken); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k.Lock()
|
||||
k.accessToken = accessToken
|
||||
k.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupUser lookup user by their userid.
|
||||
func (k *KeycloakProvider) LookupUser(userid string) (User, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, k.adminURL, nil)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
req.URL.Path = path.Join(req.URL.Path, "realms", k.realm, "users", userid)
|
||||
|
||||
k.Lock()
|
||||
accessToken := k.accessToken
|
||||
k.Unlock()
|
||||
if accessToken.AccessToken == "" {
|
||||
return User{}, ErrAccessTokenExpired
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken.AccessToken)
|
||||
resp, err := k.client.Do(req)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK, http.StatusPartialContent:
|
||||
var u User
|
||||
if err = json.NewDecoder(resp.Body).Decode(&u); err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
return u, nil
|
||||
case http.StatusNotFound:
|
||||
return User{
|
||||
ID: userid,
|
||||
Enabled: false,
|
||||
}, nil
|
||||
case http.StatusUnauthorized:
|
||||
return User{}, ErrAccessTokenExpired
|
||||
}
|
||||
return User{}, fmt.Errorf("Unable to lookup - keycloak user lookup returned %v", resp.Status)
|
||||
}
|
||||
|
||||
// Option is a function type that accepts a pointer Target
|
||||
type Option func(*KeycloakProvider)
|
||||
|
||||
// WithTransport provide custom transport
|
||||
func WithTransport(transport http.RoundTripper) Option {
|
||||
return func(p *KeycloakProvider) {
|
||||
p.client = http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithOpenIDConfig provide OpenID Endpoint configuration discovery document
|
||||
func WithOpenIDConfig(oeConfig DiscoveryDoc) Option {
|
||||
return func(p *KeycloakProvider) {
|
||||
p.oeConfig = oeConfig
|
||||
}
|
||||
}
|
||||
|
||||
// WithAdminURL provide admin URL configuration for Keycloak
|
||||
func WithAdminURL(url string) Option {
|
||||
return func(p *KeycloakProvider) {
|
||||
p.adminURL = url
|
||||
}
|
||||
}
|
||||
|
||||
// WithRealm provide realm configuration for Keycloak
|
||||
func WithRealm(realm string) Option {
|
||||
return func(p *KeycloakProvider) {
|
||||
p.realm = realm
|
||||
}
|
||||
}
|
||||
|
||||
// KeyCloak initializes a new keycloak provider
|
||||
func KeyCloak(opts ...Option) (Provider, error) {
|
||||
p := &KeycloakProvider{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
}
|
||||
|
||||
if p.adminURL == "" {
|
||||
return nil, errors.New("Admin URL cannot be empty")
|
||||
}
|
||||
|
||||
_, err := url.Parse(p.adminURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to parse the adminURL %s: %w", p.adminURL, err)
|
||||
}
|
||||
|
||||
if p.client.Transport == nil {
|
||||
p.client.Transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
if p.oeConfig.TokenEndpoint == "" {
|
||||
return nil, errors.New("missing OpenID token endpoint")
|
||||
}
|
||||
|
||||
if p.realm == "" {
|
||||
p.realm = "master" // default realm
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
62
internal/config/identity/openid/provider/provider.go
Normal file
62
internal/config/identity/openid/provider/provider.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// 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 provider
|
||||
|
||||
import "errors"
|
||||
|
||||
// DiscoveryDoc - parses the output from openid-configuration
|
||||
// for example https://accounts.google.com/.well-known/openid-configuration
|
||||
//
|
||||
//nolint:unused
|
||||
type DiscoveryDoc struct {
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
AuthEndpoint string `json:"authorization_endpoint,omitempty"`
|
||||
TokenEndpoint string `json:"token_endpoint,omitempty"`
|
||||
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
|
||||
UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
|
||||
JwksURI string `json:"jwks_uri,omitempty"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
||||
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
TokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported,omitempty"`
|
||||
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
||||
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
|
||||
}
|
||||
|
||||
// User represents information about user.
|
||||
type User struct {
|
||||
Name string `json:"username"`
|
||||
ID string `json:"id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// Standard errors.
|
||||
var (
|
||||
ErrNotImplemented = errors.New("function not implemented")
|
||||
ErrAccessTokenExpired = errors.New("access_token expired or unauthorized")
|
||||
)
|
||||
|
||||
// Provider implements identity provider specific admin operations, such as
|
||||
// looking up users, fetching additional attributes etc.
|
||||
type Provider interface {
|
||||
LoginWithUser(username, password string) error
|
||||
LoginWithClientID(clientID, clientSecret string) error
|
||||
LookupUser(userid string) (User, error)
|
||||
}
|
||||
Reference in New Issue
Block a user