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"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt"
|
||||
"github.com/portainer/portainer/api/chisel"
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
|
@ -24,6 +24,7 @@ import (
|
|||
kubecli "github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/portainer/portainer/api/ldap"
|
||||
"github.com/portainer/portainer/api/libcompose"
|
||||
"github.com/portainer/portainer/api/oauth"
|
||||
)
|
||||
|
||||
func initCLI() *portainer.CLIFlags {
|
||||
|
@ -108,6 +109,10 @@ func initLDAPService() portainer.LDAPService {
|
|||
return &ldap.Service{}
|
||||
}
|
||||
|
||||
func initOAuthService() portainer.OAuthService {
|
||||
return oauth.NewService()
|
||||
}
|
||||
|
||||
func initGitService() portainer.GitService {
|
||||
return git.NewService()
|
||||
}
|
||||
|
@ -354,6 +359,8 @@ func main() {
|
|||
|
||||
ldapService := initLDAPService()
|
||||
|
||||
oauthService := initOAuthService()
|
||||
|
||||
gitService := initGitService()
|
||||
|
||||
cryptoService := initCryptoService()
|
||||
|
@ -467,6 +474,7 @@ func main() {
|
|||
JWTService: jwtService,
|
||||
FileService: fileService,
|
||||
LDAPService: ldapService,
|
||||
OAuthService: oauthService,
|
||||
GitService: gitService,
|
||||
SignatureService: digitalSignatureService,
|
||||
SnapshotService: snapshotService,
|
||||
|
|
|
@ -30,6 +30,7 @@ require (
|
|||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
|
||||
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1
|
||||
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/src-d/go-git.v4 v4.13.1
|
||||
k8s.io/api v0.17.2
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
|
@ -27,52 +25,22 @@ func (payload *oauthPayload) Validate(r *http.Request) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (handler *Handler) authenticateThroughExtension(code, licenseKey string, settings *portainer.OAuthSettings) (string, error) {
|
||||
extensionURL := handler.ProxyManager.GetExtensionURL(portainer.OAuthAuthenticationExtension)
|
||||
func (handler *Handler) authenticateOAuth(code string, settings *portainer.OAuthSettings) (string, error) {
|
||||
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 {
|
||||
log.Printf("[DEBUG] - Unable to authenticate user via OAuth: %v", err)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", extensionURL+"/validate", 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
|
||||
return username, nil
|
||||
}
|
||||
|
||||
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")}
|
||||
}
|
||||
|
||||
extension, err := handler.DataStore.Extension().Extension(portainer.OAuthAuthenticationExtension)
|
||||
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)
|
||||
username, err := handler.authenticateOAuth(payload.Code, &settings.OAuthSettings)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] - OAuth authentication error: %s", err)
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to authenticate through OAuth", httperrors.ErrUnauthorized}
|
||||
|
|
|
@ -19,6 +19,7 @@ type Handler struct {
|
|||
CryptoService portainer.CryptoService
|
||||
JWTService portainer.JWTService
|
||||
LDAPService portainer.LDAPService
|
||||
OAuthService portainer.OAuthService
|
||||
ProxyManager *proxy.Manager
|
||||
AuthorizationService *authorization.Service
|
||||
KubernetesTokenCacheManager *kubernetes.TokenCacheManager
|
||||
|
|
|
@ -61,6 +61,7 @@ type Server struct {
|
|||
GitService portainer.GitService
|
||||
JWTService portainer.JWTService
|
||||
LDAPService portainer.LDAPService
|
||||
OAuthService portainer.OAuthService
|
||||
SwarmStackManager portainer.SwarmStackManager
|
||||
Handler *handler.Handler
|
||||
SSL bool
|
||||
|
@ -90,6 +91,7 @@ func (server *Server) Start() error {
|
|||
authHandler.ProxyManager = proxyManager
|
||||
authHandler.AuthorizationService = authorizationService
|
||||
authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager
|
||||
authHandler.OAuthService = server.OAuthService
|
||||
|
||||
var roleHandler = roles.NewHandler(requestBouncer)
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 interface {
|
||||
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',
|
||||
function ($stateRegistryProvider) {
|
||||
'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;
|
||||
|
||||
this.providers = [
|
||||
|
@ -9,6 +9,9 @@ angular.module('portainer.extensions.oauth').controller('OAuthProviderSelectorCo
|
|||
userIdentifier: 'userPrincipalName',
|
||||
scopes: 'id,email,name',
|
||||
name: 'microsoft',
|
||||
label: 'Microsoft',
|
||||
description: 'Microsoft OAuth provider',
|
||||
icon: 'fab fa-microsoft',
|
||||
},
|
||||
{
|
||||
authUrl: 'https://accounts.google.com/o/oauth2/auth',
|
||||
|
@ -17,6 +20,9 @@ angular.module('portainer.extensions.oauth').controller('OAuthProviderSelectorCo
|
|||
userIdentifier: 'email',
|
||||
scopes: 'profile email',
|
||||
name: 'google',
|
||||
label: 'Google',
|
||||
description: 'Google OAuth provider',
|
||||
icon: 'fab fa-google',
|
||||
},
|
||||
{
|
||||
authUrl: 'https://github.com/login/oauth/authorize',
|
||||
|
@ -25,6 +31,9 @@ angular.module('portainer.extensions.oauth').controller('OAuthProviderSelectorCo
|
|||
userIdentifier: 'login',
|
||||
scopes: 'id email name',
|
||||
name: 'github',
|
||||
label: 'Github',
|
||||
description: 'Github OAuth provider',
|
||||
icon: 'fab fa-github',
|
||||
},
|
||||
{
|
||||
authUrl: '',
|
||||
|
@ -33,6 +42,9 @@ angular.module('portainer.extensions.oauth').controller('OAuthProviderSelectorCo
|
|||
userIdentifier: '',
|
||||
scopes: '',
|
||||
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',
|
||||
bindings: {
|
||||
onSelect: '<',
|
|
@ -1,6 +1,6 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.extensions.oauth').controller('OAuthSettingsController', function OAuthSettingsController() {
|
||||
angular.module('portainer.oauth').controller('OAuthSettingsController', function OAuthSettingsController() {
|
||||
var ctrl = this;
|
||||
|
||||
this.state = {
|
|
@ -1,4 +1,4 @@
|
|||
angular.module('portainer.extensions.oauth').component('oauthSettings', {
|
||||
angular.module('portainer.oauth').component('oauthSettings', {
|
||||
templateUrl: './oauth-settings.html',
|
||||
bindings: {
|
||||
settings: '=',
|
|
@ -1,4 +1,4 @@
|
|||
angular.module('portainer.extensions.oauth').factory('OAuth', [
|
||||
angular.module('portainer.oauth').factory('OAuth', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_OAUTH',
|
||||
function OAuthFactory($resource, API_ENDPOINT_OAUTH) {
|
|
@ -57,7 +57,7 @@
|
|||
<p>LDAP authentication</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="oauthAuthenticationAvailable">
|
||||
<div>
|
||||
<input type="radio" id="registry_auth" ng-model="settings.AuthenticationMethod" ng-value="3" />
|
||||
<label for="registry_auth">
|
||||
<div class="boxselector_header">
|
||||
|
@ -67,23 +67,6 @@
|
|||
<p>OAuth authentication</p>
|
||||
</label>
|
||||
</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>
|
||||
|
||||
|
|
|
@ -6,8 +6,7 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
|||
'SettingsService',
|
||||
'FileUploadService',
|
||||
'TeamService',
|
||||
'ExtensionService',
|
||||
function ($q, $scope, $state, Notifications, SettingsService, FileUploadService, TeamService, ExtensionService) {
|
||||
function ($q, $scope, $state, Notifications, SettingsService, FileUploadService, TeamService) {
|
||||
$scope.state = {
|
||||
successfulConnectivityCheck: 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() {
|
||||
return $scope.settings && $scope.settings.AuthenticationMethod === 3;
|
||||
};
|
||||
|
@ -167,7 +162,6 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
|||
$q.all({
|
||||
settings: SettingsService.settings(),
|
||||
teams: TeamService.teams(),
|
||||
oauthAuthentication: ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.OAUTH_AUTHENTICATION),
|
||||
})
|
||||
.then(function success(data) {
|
||||
var settings = data.settings;
|
||||
|
@ -176,7 +170,6 @@ angular.module('portainer.app').controller('SettingsAuthenticationController', [
|
|||
$scope.formValues.LDAPSettings = settings.LDAPSettings;
|
||||
$scope.OAuthSettings = settings.OAuthSettings;
|
||||
$scope.formValues.TLSCACert = settings.LDAPSettings.TLSConfig.TLSCACert;
|
||||
$scope.oauthAuthenticationAvailable = data.oauthAuthentication;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
|
|
Loading…
Reference in New Issue