mirror of https://github.com/portainer/portainer
feat(auth): integrate oauth extension (#4152)
* refactor(oauth): move oauth client code * feat(oauth): move extension code into server code * feat(oauth): enable oauth without extension * refactor(oauth): make it easier to remove providerspull/4156/head
parent
148ccd1bc4
commit
00f4fe0039
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
"github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/bolt"
|
"github.com/portainer/portainer/api/bolt"
|
||||||
"github.com/portainer/portainer/api/chisel"
|
"github.com/portainer/portainer/api/chisel"
|
||||||
"github.com/portainer/portainer/api/cli"
|
"github.com/portainer/portainer/api/cli"
|
||||||
|
@ -24,6 +24,7 @@ import (
|
||||||
kubecli "github.com/portainer/portainer/api/kubernetes/cli"
|
kubecli "github.com/portainer/portainer/api/kubernetes/cli"
|
||||||
"github.com/portainer/portainer/api/ldap"
|
"github.com/portainer/portainer/api/ldap"
|
||||||
"github.com/portainer/portainer/api/libcompose"
|
"github.com/portainer/portainer/api/libcompose"
|
||||||
|
"github.com/portainer/portainer/api/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initCLI() *portainer.CLIFlags {
|
func initCLI() *portainer.CLIFlags {
|
||||||
|
@ -108,6 +109,10 @@ func initLDAPService() portainer.LDAPService {
|
||||||
return &ldap.Service{}
|
return &ldap.Service{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initOAuthService() portainer.OAuthService {
|
||||||
|
return oauth.NewService()
|
||||||
|
}
|
||||||
|
|
||||||
func initGitService() portainer.GitService {
|
func initGitService() portainer.GitService {
|
||||||
return git.NewService()
|
return git.NewService()
|
||||||
}
|
}
|
||||||
|
@ -354,6 +359,8 @@ func main() {
|
||||||
|
|
||||||
ldapService := initLDAPService()
|
ldapService := initLDAPService()
|
||||||
|
|
||||||
|
oauthService := initOAuthService()
|
||||||
|
|
||||||
gitService := initGitService()
|
gitService := initGitService()
|
||||||
|
|
||||||
cryptoService := initCryptoService()
|
cryptoService := initCryptoService()
|
||||||
|
@ -467,6 +474,7 @@ func main() {
|
||||||
JWTService: jwtService,
|
JWTService: jwtService,
|
||||||
FileService: fileService,
|
FileService: fileService,
|
||||||
LDAPService: ldapService,
|
LDAPService: ldapService,
|
||||||
|
OAuthService: oauthService,
|
||||||
GitService: gitService,
|
GitService: gitService,
|
||||||
SignatureService: digitalSignatureService,
|
SignatureService: digitalSignatureService,
|
||||||
SnapshotService: snapshotService,
|
SnapshotService: snapshotService,
|
||||||
|
|
|
@ -30,6 +30,7 @@ require (
|
||||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
|
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
|
||||||
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1
|
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1
|
||||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||||
k8s.io/api v0.17.2
|
k8s.io/api v0.17.2
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -27,52 +25,22 @@ func (payload *oauthPayload) Validate(r *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) authenticateThroughExtension(code, licenseKey string, settings *portainer.OAuthSettings) (string, error) {
|
func (handler *Handler) authenticateOAuth(code string, settings *portainer.OAuthSettings) (string, error) {
|
||||||
extensionURL := handler.ProxyManager.GetExtensionURL(portainer.OAuthAuthenticationExtension)
|
if code == "" {
|
||||||
|
return "", errors.New("Invalid OAuth authorization code")
|
||||||
|
}
|
||||||
|
|
||||||
encodedConfiguration, err := json.Marshal(settings)
|
if settings == nil {
|
||||||
|
return "", errors.New("Invalid OAuth configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
username, err := handler.OAuthService.Authenticate(code, settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("[DEBUG] - Unable to authenticate user via OAuth: %v", err)
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", extensionURL+"/validate", nil)
|
return username, nil
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
req.Header.Set("X-OAuth-Config", string(encodedConfiguration))
|
|
||||||
req.Header.Set("X-OAuth-Code", code)
|
|
||||||
req.Header.Set("X-PortainerExtension-License", licenseKey)
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
type extensionResponse struct {
|
|
||||||
Username string `json:"Username,omitempty"`
|
|
||||||
Err string `json:"err,omitempty"`
|
|
||||||
Details string `json:"details,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var extResp extensionResponse
|
|
||||||
err = json.Unmarshal(body, &extResp)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return "", errors.New(extResp.Err + ":" + extResp.Details)
|
|
||||||
}
|
|
||||||
|
|
||||||
return extResp.Username, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
|
@ -91,14 +59,7 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "OAuth authentication is not enabled", errors.New("OAuth authentication is not enabled")}
|
return &httperror.HandlerError{http.StatusForbidden, "OAuth authentication is not enabled", errors.New("OAuth authentication is not enabled")}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension, err := handler.DataStore.Extension().Extension(portainer.OAuthAuthenticationExtension)
|
username, err := handler.authenticateOAuth(payload.Code, &settings.OAuthSettings)
|
||||||
if err == bolterrors.ErrObjectNotFound {
|
|
||||||
return &httperror.HandlerError{http.StatusNotFound, "Oauth authentication extension is not enabled", err}
|
|
||||||
} else if err != nil {
|
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
username, err := handler.authenticateThroughExtension(payload.Code, extension.License.LicenseKey, &settings.OAuthSettings)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[DEBUG] - OAuth authentication error: %s", err)
|
log.Printf("[DEBUG] - OAuth authentication error: %s", err)
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to authenticate through OAuth", httperrors.ErrUnauthorized}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to authenticate through OAuth", httperrors.ErrUnauthorized}
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Handler struct {
|
||||||
CryptoService portainer.CryptoService
|
CryptoService portainer.CryptoService
|
||||||
JWTService portainer.JWTService
|
JWTService portainer.JWTService
|
||||||
LDAPService portainer.LDAPService
|
LDAPService portainer.LDAPService
|
||||||
|
OAuthService portainer.OAuthService
|
||||||
ProxyManager *proxy.Manager
|
ProxyManager *proxy.Manager
|
||||||
AuthorizationService *authorization.Service
|
AuthorizationService *authorization.Service
|
||||||
KubernetesTokenCacheManager *kubernetes.TokenCacheManager
|
KubernetesTokenCacheManager *kubernetes.TokenCacheManager
|
||||||
|
|
|
@ -61,6 +61,7 @@ type Server struct {
|
||||||
GitService portainer.GitService
|
GitService portainer.GitService
|
||||||
JWTService portainer.JWTService
|
JWTService portainer.JWTService
|
||||||
LDAPService portainer.LDAPService
|
LDAPService portainer.LDAPService
|
||||||
|
OAuthService portainer.OAuthService
|
||||||
SwarmStackManager portainer.SwarmStackManager
|
SwarmStackManager portainer.SwarmStackManager
|
||||||
Handler *handler.Handler
|
Handler *handler.Handler
|
||||||
SSL bool
|
SSL bool
|
||||||
|
@ -90,6 +91,7 @@ func (server *Server) Start() error {
|
||||||
authHandler.ProxyManager = proxyManager
|
authHandler.ProxyManager = proxyManager
|
||||||
authHandler.AuthorizationService = authorizationService
|
authHandler.AuthorizationService = authorizationService
|
||||||
authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager
|
authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager
|
||||||
|
authHandler.OAuthService = server.OAuthService
|
||||||
|
|
||||||
var roleHandler = roles.NewHandler(requestBouncer)
|
var roleHandler = roles.NewHandler(requestBouncer)
|
||||||
roleHandler.DataStore = server.DataStore
|
roleHandler.DataStore = server.DataStore
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
package oauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/portainer/portainer/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service represents a service used to authenticate users against an authorization server
|
||||||
|
type Service struct{}
|
||||||
|
|
||||||
|
// NewService returns a pointer to a new instance of this service
|
||||||
|
func NewService() *Service {
|
||||||
|
return &Service{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate takes an access code and exchanges it for an access token from portainer OAuthSettings token endpoint.
|
||||||
|
// On success, it will then return the username associated to authenticated user by fetching this information
|
||||||
|
// from the resource server and matching it with the user identifier setting.
|
||||||
|
func (*Service) Authenticate(code string, configuration *portainer.OAuthSettings) (string, error) {
|
||||||
|
token, err := getAccessToken(code, configuration)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[DEBUG] - Failed retrieving access token: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getUsername(token, configuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccessToken(code string, configuration *portainer.OAuthSettings) (string, error) {
|
||||||
|
unescapedCode, err := url.QueryUnescape(code)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := buildConfig(configuration)
|
||||||
|
token, err := config.Exchange(context.Background(), unescapedCode)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.AccessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUsername(token string, configuration *portainer.OAuthSettings) (string, error) {
|
||||||
|
req, err := http.NewRequest("GET", configuration.ResourceURI, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", &oauth2.RetrieveError{
|
||||||
|
Response: resp,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if content == "application/x-www-form-urlencoded" || content == "text/plain" {
|
||||||
|
values, err := url.ParseQuery(string(body))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
username := values.Get(configuration.UserIdentifier)
|
||||||
|
return username, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var datamap map[string]interface{}
|
||||||
|
if err = json.Unmarshal(body, &datamap); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
username, ok := datamap[configuration.UserIdentifier].(string)
|
||||||
|
if ok && username != "" {
|
||||||
|
return username, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
username, ok := datamap[configuration.UserIdentifier].(float64)
|
||||||
|
if ok && username != 0 {
|
||||||
|
return fmt.Sprint(int(username)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", &oauth2.RetrieveError{
|
||||||
|
Response: resp,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildConfig(configuration *portainer.OAuthSettings) *oauth2.Config {
|
||||||
|
endpoint := oauth2.Endpoint{
|
||||||
|
AuthURL: configuration.AuthorizationURI,
|
||||||
|
TokenURL: configuration.AccessTokenURI,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oauth2.Config{
|
||||||
|
ClientID: configuration.ClientID,
|
||||||
|
ClientSecret: configuration.ClientSecret,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
RedirectURL: configuration.RedirectURI,
|
||||||
|
Scopes: []string{configuration.Scopes},
|
||||||
|
}
|
||||||
|
}
|
|
@ -984,6 +984,11 @@ type (
|
||||||
GetUserGroups(username string, settings *LDAPSettings) ([]string, error)
|
GetUserGroups(username string, settings *LDAPSettings) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuthService represents a service used to authenticate users using OAuth
|
||||||
|
OAuthService interface {
|
||||||
|
Authenticate(code string, configuration *OAuthSettings) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
// RegistryService represents a service for managing registry data
|
// RegistryService represents a service for managing registry data
|
||||||
RegistryService interface {
|
RegistryService interface {
|
||||||
Registry(ID RegistryID) (*Registry, error)
|
Registry(ID RegistryID) (*Registry, error)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
angular.module('portainer.extensions', ['portainer.extensions.registrymanagement', 'portainer.extensions.oauth', 'portainer.extensions.rbac']);
|
angular.module('portainer.extensions', ['portainer.extensions.registrymanagement', 'portainer.extensions.rbac']);
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
angular.module('portainer.extensions.oauth', ['ngResource']).constant('API_ENDPOINT_OAUTH', 'api/auth/oauth');
|
|
|
@ -1,49 +0,0 @@
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Provider
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group"></div>
|
|
||||||
<div class="form-group" style="margin-bottom: 0;">
|
|
||||||
<div class="boxselector_wrapper">
|
|
||||||
<div ng-click="$ctrl.onSelect($ctrl.provider, true)">
|
|
||||||
<input type="radio" id="oauth_provider_microsoft" ng-model="$ctrl.provider" ng-value="$ctrl.providers[0]" />
|
|
||||||
<label for="oauth_provider_microsoft">
|
|
||||||
<div class="boxselector_header">
|
|
||||||
<i class="fab fa-microsoft" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
Microsoft
|
|
||||||
</div>
|
|
||||||
<p>Microsoft OAuth provider</p>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div ng-click="$ctrl.onSelect($ctrl.provider, true)">
|
|
||||||
<input type="radio" id="oauth_provider_google" ng-model="$ctrl.provider" ng-value="$ctrl.providers[1]" />
|
|
||||||
<label for="oauth_provider_google">
|
|
||||||
<div class="boxselector_header">
|
|
||||||
<i class="fab fa-google" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
Google
|
|
||||||
</div>
|
|
||||||
<p>Google OAuth provider</p>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div ng-click="$ctrl.onSelect($ctrl.provider, true)">
|
|
||||||
<input type="radio" id="oauth_provider_github" ng-model="$ctrl.provider" ng-value="$ctrl.providers[2]" />
|
|
||||||
<label for="oauth_provider_github">
|
|
||||||
<div class="boxselector_header">
|
|
||||||
<i class="fab fa-github" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
Github
|
|
||||||
</div>
|
|
||||||
<p>Github OAuth provider</p>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div ng-click="$ctrl.onSelect($ctrl.provider, true)">
|
|
||||||
<input type="radio" id="oauth_provider_custom" ng-model="$ctrl.provider" ng-value="$ctrl.providers[3]" />
|
|
||||||
<label for="oauth_provider_custom">
|
|
||||||
<div class="boxselector_header">
|
|
||||||
<i class="fa fa-user-check" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
Custom
|
|
||||||
</div>
|
|
||||||
<p>Custom OAuth provider</p>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -25,7 +25,7 @@ function initAnalytics(Analytics, $rootScope) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
angular.module('portainer.app', []).config([
|
angular.module('portainer.app', ['portainer.oauth']).config([
|
||||||
'$stateRegistryProvider',
|
'$stateRegistryProvider',
|
||||||
function ($stateRegistryProvider) {
|
function ($stateRegistryProvider) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
angular.module('portainer.oauth', ['ngResource']).constant('API_ENDPOINT_OAUTH', 'api/auth/oauth');
|
|
@ -1,4 +1,4 @@
|
||||||
angular.module('portainer.extensions.oauth').controller('OAuthProviderSelectorController', function OAuthProviderSelectorController() {
|
angular.module('portainer.oauth').controller('OAuthProviderSelectorController', function OAuthProviderSelectorController() {
|
||||||
var ctrl = this;
|
var ctrl = this;
|
||||||
|
|
||||||
this.providers = [
|
this.providers = [
|
||||||
|
@ -9,6 +9,9 @@ angular.module('portainer.extensions.oauth').controller('OAuthProviderSelectorCo
|
||||||
userIdentifier: 'userPrincipalName',
|
userIdentifier: 'userPrincipalName',
|
||||||
scopes: 'id,email,name',
|
scopes: 'id,email,name',
|
||||||
name: 'microsoft',
|
name: 'microsoft',
|
||||||
|
label: 'Microsoft',
|
||||||
|
description: 'Microsoft OAuth provider',
|
||||||
|
icon: 'fab fa-microsoft',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
authUrl: 'https://accounts.google.com/o/oauth2/auth',
|
authUrl: 'https://accounts.google.com/o/oauth2/auth',
|
||||||
|
@ -17,6 +20,9 @@ angular.module('portainer.extensions.oauth').controller('OAuthProviderSelectorCo
|
||||||
userIdentifier: 'email',
|
userIdentifier: 'email',
|
||||||
scopes: 'profile email',
|
scopes: 'profile email',
|
||||||
name: 'google',
|
name: 'google',
|
||||||
|
label: 'Google',
|
||||||
|
description: 'Google OAuth provider',
|
||||||
|
icon: 'fab fa-google',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
authUrl: 'https://github.com/login/oauth/authorize',
|
authUrl: 'https://github.com/login/oauth/authorize',
|
||||||
|
@ -25,6 +31,9 @@ angular.module('portainer.extensions.oauth').controller('OAuthProviderSelectorCo
|
||||||
userIdentifier: 'login',
|
userIdentifier: 'login',
|
||||||
scopes: 'id email name',
|
scopes: 'id email name',
|
||||||
name: 'github',
|
name: 'github',
|
||||||
|
label: 'Github',
|
||||||
|
description: 'Github OAuth provider',
|
||||||
|
icon: 'fab fa-github',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
authUrl: '',
|
authUrl: '',
|
||||||
|
@ -33,6 +42,9 @@ angular.module('portainer.extensions.oauth').controller('OAuthProviderSelectorCo
|
||||||
userIdentifier: '',
|
userIdentifier: '',
|
||||||
scopes: '',
|
scopes: '',
|
||||||
name: 'custom',
|
name: 'custom',
|
||||||
|
label: 'Custom',
|
||||||
|
description: 'Custom OAuth provider',
|
||||||
|
icon: 'fa fa-user-check',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
Provider
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group"></div>
|
||||||
|
<div class="form-group" style="margin-bottom: 0;">
|
||||||
|
<div class="boxselector_wrapper">
|
||||||
|
<div ng-repeat="provider in $ctrl.providers" ng-click="$ctrl.onSelect(provider, true)">
|
||||||
|
<input type="radio" id="{{ 'oauth_provider_' + provider.name }}" ng-model="$ctrl.provider" ng-value="provider" />
|
||||||
|
<label for="{{ 'oauth_provider_' + provider.name }}">
|
||||||
|
<div class="boxselector_header">
|
||||||
|
<i ng-class="provider.icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
{{ provider.label }}
|
||||||
|
</div>
|
||||||
|
<p>{{ provider.description }}</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,4 +1,4 @@
|
||||||
angular.module('portainer.extensions.oauth').component('oauthProvidersSelector', {
|
angular.module('portainer.oauth').component('oauthProvidersSelector', {
|
||||||
templateUrl: './oauth-providers-selector.html',
|
templateUrl: './oauth-providers-selector.html',
|
||||||
bindings: {
|
bindings: {
|
||||||
onSelect: '<',
|
onSelect: '<',
|
|
@ -1,6 +1,6 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
angular.module('portainer.extensions.oauth').controller('OAuthSettingsController', function OAuthSettingsController() {
|
angular.module('portainer.oauth').controller('OAuthSettingsController', function OAuthSettingsController() {
|
||||||
var ctrl = this;
|
var ctrl = this;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
|
@ -1,4 +1,4 @@
|
||||||
angular.module('portainer.extensions.oauth').component('oauthSettings', {
|
angular.module('portainer.oauth').component('oauthSettings', {
|
||||||
templateUrl: './oauth-settings.html',
|
templateUrl: './oauth-settings.html',
|
||||||
bindings: {
|
bindings: {
|
||||||
settings: '=',
|
settings: '=',
|
|
@ -1,4 +1,4 @@
|
||||||
angular.module('portainer.extensions.oauth').factory('OAuth', [
|
angular.module('portainer.oauth').factory('OAuth', [
|
||||||
'$resource',
|
'$resource',
|
||||||
'API_ENDPOINT_OAUTH',
|
'API_ENDPOINT_OAUTH',
|
||||||
function OAuthFactory($resource, API_ENDPOINT_OAUTH) {
|
function OAuthFactory($resource, API_ENDPOINT_OAUTH) {
|
|
@ -57,7 +57,7 @@
|
||||||
<p>LDAP authentication</p>
|
<p>LDAP authentication</p>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="oauthAuthenticationAvailable">
|
<div>
|
||||||
<input type="radio" id="registry_auth" ng-model="settings.AuthenticationMethod" ng-value="3" />
|
<input type="radio" id="registry_auth" ng-model="settings.AuthenticationMethod" ng-value="3" />
|
||||||
<label for="registry_auth">
|
<label for="registry_auth">
|
||||||
<div class="boxselector_header">
|
<div class="boxselector_header">
|
||||||
|
@ -67,23 +67,6 @@
|
||||||
<p>OAuth authentication</p>
|
<p>OAuth authentication</p>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div style="color: #767676;" ng-click="goToOAuthExtensionView()" ng-if="!oauthAuthenticationAvailable">
|
|
||||||
<input type="radio" id="registry_auth" ng-model="settings.AuthenticationMethod" ng-value="3" disabled />
|
|
||||||
<label
|
|
||||||
for="registry_auth"
|
|
||||||
tooltip-append-to-body="true"
|
|
||||||
tooltip-placement="bottom"
|
|
||||||
tooltip-class="portainer-tooltip"
|
|
||||||
uib-tooltip="Feature available via an extension"
|
|
||||||
style="cursor: pointer; border-color: #767676;"
|
|
||||||
>
|
|
||||||
<div class="boxselector_header">
|
|
||||||
<i class="fa fa-users" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
OAuth (extension)
|
|
||||||
</div>
|
|
||||||
<p>OAuth authentication</p>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,7 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
||||||
'SettingsService',
|
'SettingsService',
|
||||||
'FileUploadService',
|
'FileUploadService',
|
||||||
'TeamService',
|
'TeamService',
|
||||||
'ExtensionService',
|
function ($q, $scope, $state, Notifications, SettingsService, FileUploadService, TeamService) {
|
||||||
function ($q, $scope, $state, Notifications, SettingsService, FileUploadService, TeamService, ExtensionService) {
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
successfulConnectivityCheck: false,
|
successfulConnectivityCheck: false,
|
||||||
failedConnectivityCheck: false,
|
failedConnectivityCheck: false,
|
||||||
|
@ -68,10 +67,6 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.goToOAuthExtensionView = function () {
|
|
||||||
$state.go('portainer.extensions.extension', { id: 2 });
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.isOauthEnabled = function isOauthEnabled() {
|
$scope.isOauthEnabled = function isOauthEnabled() {
|
||||||
return $scope.settings && $scope.settings.AuthenticationMethod === 3;
|
return $scope.settings && $scope.settings.AuthenticationMethod === 3;
|
||||||
};
|
};
|
||||||
|
@ -167,7 +162,6 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
||||||
$q.all({
|
$q.all({
|
||||||
settings: SettingsService.settings(),
|
settings: SettingsService.settings(),
|
||||||
teams: TeamService.teams(),
|
teams: TeamService.teams(),
|
||||||
oauthAuthentication: ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.OAUTH_AUTHENTICATION),
|
|
||||||
})
|
})
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var settings = data.settings;
|
var settings = data.settings;
|
||||||
|
@ -176,7 +170,6 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
||||||
$scope.formValues.LDAPSettings = settings.LDAPSettings;
|
$scope.formValues.LDAPSettings = settings.LDAPSettings;
|
||||||
$scope.OAuthSettings = settings.OAuthSettings;
|
$scope.OAuthSettings = settings.OAuthSettings;
|
||||||
$scope.formValues.TLSCACert = settings.LDAPSettings.TLSConfig.TLSCACert;
|
$scope.formValues.TLSCACert = settings.LDAPSettings.TLSConfig.TLSCACert;
|
||||||
$scope.oauthAuthenticationAvailable = data.oauthAuthentication;
|
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||||
|
|
Loading…
Reference in New Issue