mirror of https://github.com/portainer/portainer
feat(oauth): dev build supporting Oauth extension
parent
086bad2956
commit
7643f8d08c
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/portainer/portainer/jwt"
|
||||
"github.com/portainer/portainer/ldap"
|
||||
"github.com/portainer/portainer/libcompose"
|
||||
"github.com/portainer/portainer/oauth"
|
||||
|
||||
"log"
|
||||
)
|
||||
|
@ -101,10 +100,6 @@ func initLDAPService() portainer.LDAPService {
|
|||
return &ldap.Service{}
|
||||
}
|
||||
|
||||
func initOAuthService() portainer.OAuthService {
|
||||
return &oauth.Service{}
|
||||
}
|
||||
|
||||
func initGitService() portainer.GitService {
|
||||
return &git.Service{}
|
||||
}
|
||||
|
@ -529,8 +524,6 @@ func main() {
|
|||
|
||||
ldapService := initLDAPService()
|
||||
|
||||
oauthService := initOAuthService()
|
||||
|
||||
gitService := initGitService()
|
||||
|
||||
cryptoService := initCryptoService()
|
||||
|
@ -676,7 +669,6 @@ func main() {
|
|||
JWTService: jwtService,
|
||||
FileService: fileService,
|
||||
LDAPService: ldapService,
|
||||
OAuthService: oauthService,
|
||||
GitService: gitService,
|
||||
SignatureService: digitalSignatureService,
|
||||
JobScheduler: jobScheduler,
|
||||
|
|
|
@ -18,7 +18,8 @@ import (
|
|||
var extensionDownloadBaseURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com/extensions/"
|
||||
|
||||
var extensionBinaryMap = map[portainer.ExtensionID]string{
|
||||
portainer.RegistryManagementExtension: "extension-registry-management",
|
||||
portainer.RegistryManagementExtension: "extension-registry-management",
|
||||
portainer.OAuthAuthenticationExtension: "extension-oauth-authentication",
|
||||
}
|
||||
|
||||
// ExtensionManager represents a service used to
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"log"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
|
@ -21,6 +22,54 @@ 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)
|
||||
|
||||
encodedConfiguration, err := json.Marshal(settings)
|
||||
if err != nil {
|
||||
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 "", portainer.Error(extResp.Err + ":" + extResp.Details)
|
||||
}
|
||||
|
||||
return extResp.Username, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload oauthPayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
|
@ -37,16 +86,16 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
|
|||
return &httperror.HandlerError{http.StatusForbidden, "OAuth authentication is not enabled", err}
|
||||
}
|
||||
|
||||
token, err := handler.OAuthService.GetAccessToken(payload.Code, &settings.OAuthSettings)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] - Failed retrieving access token: %v", err)
|
||||
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid access token", portainer.ErrUnauthorized}
|
||||
extension, err := handler.ExtensionService.Extension(portainer.OAuthAuthenticationExtension)
|
||||
if err == portainer.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.OAuthService.GetUsername(token, &settings.OAuthSettings)
|
||||
username, err := handler.authenticateThroughExtension(payload.Code, extension.License.LicenseKey, &settings.OAuthSettings)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] - Failed acquiring username: %v", err)
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Unable to acquire username", portainer.ErrUnauthorized}
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to authenticate through OAuth", portainer.ErrUnauthorized}
|
||||
}
|
||||
|
||||
user, err := handler.UserService.UserByUsername(username)
|
||||
|
@ -85,18 +134,3 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
|
|||
|
||||
return handler.writeToken(w, user)
|
||||
}
|
||||
|
||||
func (handler *Handler) loginOAuth(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
settings, err := handler.SettingsService.Settings()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
if settings.AuthenticationMethod != 3 {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "OAuth authentication is disabled", err}
|
||||
}
|
||||
|
||||
url := handler.OAuthService.BuildLoginURL(&settings.OAuthSettings)
|
||||
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
)
|
||||
|
||||
|
@ -25,10 +26,11 @@ type Handler struct {
|
|||
CryptoService portainer.CryptoService
|
||||
JWTService portainer.JWTService
|
||||
LDAPService portainer.LDAPService
|
||||
OAuthService portainer.OAuthService
|
||||
SettingsService portainer.SettingsService
|
||||
TeamService portainer.TeamService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
ExtensionService portainer.ExtensionService
|
||||
ProxyManager *proxy.Manager
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage authentication operations.
|
||||
|
@ -38,8 +40,6 @@ func NewHandler(bouncer *security.RequestBouncer, rateLimiter *security.RateLimi
|
|||
authDisabled: authDisabled,
|
||||
}
|
||||
|
||||
h.Handle("/auth/oauth/login",
|
||||
rateLimiter.LimitAccess(bouncer.PublicAccess(httperror.LoggerHandler(h.loginOAuth)))).Methods(http.MethodGet)
|
||||
h.Handle("/auth/oauth/validate",
|
||||
rateLimiter.LimitAccess(bouncer.PublicAccess(httperror.LoggerHandler(h.validateOAuth)))).Methods(http.MethodPost)
|
||||
h.Handle("/auth",
|
||||
|
|
|
@ -19,7 +19,6 @@ type Handler struct {
|
|||
*mux.Router
|
||||
SettingsService portainer.SettingsService
|
||||
LDAPService portainer.LDAPService
|
||||
OAuthService portainer.OAuthService
|
||||
FileService portainer.FileService
|
||||
JobScheduler portainer.JobScheduler
|
||||
ScheduleService portainer.ScheduleService
|
||||
|
|
|
@ -33,6 +33,7 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
|||
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
|
||||
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
||||
ExternalTemplates: false,
|
||||
// TODO: check if state=portainer useful or not
|
||||
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&state=portainer",
|
||||
settings.OAuthSettings.AuthorizationURI,
|
||||
settings.OAuthSettings.ClientID,
|
||||
|
|
|
@ -12,7 +12,8 @@ import (
|
|||
// TODO: contain code related to legacy extension management
|
||||
|
||||
var extensionPorts = map[portainer.ExtensionID]string{
|
||||
portainer.RegistryManagementExtension: "7001",
|
||||
portainer.RegistryManagementExtension: "7001",
|
||||
portainer.OAuthAuthenticationExtension: "7002",
|
||||
}
|
||||
|
||||
type (
|
||||
|
@ -103,6 +104,10 @@ func (manager *Manager) CreateExtensionProxy(extensionID portainer.ExtensionID)
|
|||
return proxy, nil
|
||||
}
|
||||
|
||||
func (manager *Manager) GetExtensionURL(extensionID portainer.ExtensionID) string {
|
||||
return "http://localhost:" + extensionPorts[extensionID]
|
||||
}
|
||||
|
||||
// DeleteExtensionProxy deletes the extension proxy associated to an extension identifier
|
||||
func (manager *Manager) DeleteExtensionProxy(extensionID portainer.ExtensionID) {
|
||||
manager.extensionProxies.Remove(strconv.Itoa(int(extensionID)))
|
||||
|
|
|
@ -55,7 +55,6 @@ type Server struct {
|
|||
GitService portainer.GitService
|
||||
JWTService portainer.JWTService
|
||||
LDAPService portainer.LDAPService
|
||||
OAuthService portainer.OAuthService
|
||||
ExtensionService portainer.ExtensionService
|
||||
RegistryService portainer.RegistryService
|
||||
ResourceControlService portainer.ResourceControlService
|
||||
|
@ -105,10 +104,11 @@ func (server *Server) Start() error {
|
|||
authHandler.CryptoService = server.CryptoService
|
||||
authHandler.JWTService = server.JWTService
|
||||
authHandler.LDAPService = server.LDAPService
|
||||
authHandler.OAuthService = server.OAuthService
|
||||
authHandler.SettingsService = server.SettingsService
|
||||
authHandler.TeamService = server.TeamService
|
||||
authHandler.TeamMembershipService = server.TeamMembershipService
|
||||
authHandler.ExtensionService = server.ExtensionService
|
||||
authHandler.ProxyManager = proxyManager
|
||||
|
||||
var dockerHubHandler = dockerhub.NewHandler(requestBouncer)
|
||||
dockerHubHandler.DockerHubService = server.DockerHubService
|
||||
|
@ -157,7 +157,6 @@ func (server *Server) Start() error {
|
|||
var settingsHandler = settings.NewHandler(requestBouncer)
|
||||
settingsHandler.SettingsService = server.SettingsService
|
||||
settingsHandler.LDAPService = server.LDAPService
|
||||
settingsHandler.OAuthService = server.OAuthService
|
||||
settingsHandler.FileService = server.FileService
|
||||
settingsHandler.JobScheduler = server.JobScheduler
|
||||
settingsHandler.ScheduleService = server.ScheduleService
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrInvalidCode defines an error raised when the user authorization code is invalid
|
||||
ErrInvalidCode = portainer.Error("Invalid OAuth authorization code")
|
||||
)
|
||||
|
||||
// Service represents a service used to authenticate users against an authorization server
|
||||
type Service struct{}
|
||||
|
||||
// GetAccessToken takes an access code and exchanges it for an access token from portainer OAuthSettings token endpoint
|
||||
func (*Service) GetAccessToken(code string, settings *portainer.OAuthSettings) (string, error) {
|
||||
unescapedCode, err := url.QueryUnescape(code)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config := buildConfig(settings)
|
||||
token, err := config.Exchange(context.Background(), unescapedCode)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token.AccessToken, nil
|
||||
}
|
||||
|
||||
// GetUsername takes a token and retrieves the portainer OAuthSettings user identifier from resource server.
|
||||
func (*Service) GetUsername(token string, settings *portainer.OAuthSettings) (string, error) {
|
||||
req, err := http.NewRequest("GET", settings.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(settings.UserIdentifier)
|
||||
return username, nil
|
||||
}
|
||||
|
||||
var datamap map[string]interface{}
|
||||
if err = json.Unmarshal(body, &datamap); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
username, ok := datamap[settings.UserIdentifier].(string)
|
||||
if ok && username != "" {
|
||||
return username, nil
|
||||
}
|
||||
|
||||
if !ok {
|
||||
username, ok := datamap[settings.UserIdentifier].(float64)
|
||||
if ok && username != 0 {
|
||||
return fmt.Sprint(int(username)), nil
|
||||
}
|
||||
}
|
||||
return "", &oauth2.RetrieveError{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildLoginURL creates a login url for the oauth provider
|
||||
func (*Service) BuildLoginURL(oauthSettings *portainer.OAuthSettings) string {
|
||||
oauthConfig := buildConfig(oauthSettings)
|
||||
return oauthConfig.AuthCodeURL("portainer")
|
||||
}
|
||||
|
||||
func buildConfig(oauthSettings *portainer.OAuthSettings) *oauth2.Config {
|
||||
endpoint := oauth2.Endpoint{
|
||||
AuthURL: oauthSettings.AuthorizationURI,
|
||||
TokenURL: oauthSettings.AccessTokenURI,
|
||||
}
|
||||
|
||||
return &oauth2.Config{
|
||||
ClientID: oauthSettings.ClientID,
|
||||
ClientSecret: oauthSettings.ClientSecret,
|
||||
Endpoint: endpoint,
|
||||
RedirectURL: oauthSettings.RedirectURI,
|
||||
Scopes: []string{oauthSettings.Scopes},
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ type (
|
|||
UserIdentifier string `json:"UserIdentifier"`
|
||||
Scopes string `json:"Scopes"`
|
||||
OAuthAutoCreateUsers bool `json:"OAuthAutoCreateUsers"`
|
||||
DefaultTeamID TeamID `json:"DefaultTeamID"`
|
||||
DefaultTeamID TeamID `json:"DefaultTeamID"`
|
||||
}
|
||||
|
||||
// TLSConfiguration represents a TLS configuration
|
||||
|
@ -764,13 +764,6 @@ type (
|
|||
GetUserGroups(username string, settings *LDAPSettings) ([]string, error)
|
||||
}
|
||||
|
||||
// OAuthService represents a service used to authenticate users against an authorization server
|
||||
OAuthService interface {
|
||||
GetAccessToken(code string, settings *OAuthSettings) (string, error)
|
||||
GetUsername(token string, settings *OAuthSettings) (string, error)
|
||||
BuildLoginURL(oauthSettings *OAuthSettings) string
|
||||
}
|
||||
|
||||
// SwarmStackManager represents a service to manage Swarm stacks
|
||||
SwarmStackManager interface {
|
||||
Login(dockerhub *DockerHub, registries []Registry, endpoint *Endpoint)
|
||||
|
@ -809,7 +802,8 @@ const (
|
|||
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved
|
||||
MessageOfTheDayURL = AssetsServerURL + "/motd.html"
|
||||
// ExtensionDefinitionsURL represents the URL where Portainer extension definitions can be retrieved
|
||||
ExtensionDefinitionsURL = AssetsServerURL + "/extensions.json"
|
||||
// TODO: UPDATE URL to production URL
|
||||
ExtensionDefinitionsURL = AssetsServerURL + "/extensions-dev.json"
|
||||
// PortainerAgentHeader represents the name of the header available in any agent response
|
||||
PortainerAgentHeader = "Portainer-Agent"
|
||||
// PortainerAgentTargetHeader represent the name of the header containing the target node name
|
||||
|
@ -936,6 +930,8 @@ const (
|
|||
_ ExtensionID = iota
|
||||
// RegistryManagementExtension represents the registry management extension
|
||||
RegistryManagementExtension
|
||||
// OAuthAuthenticationExtension represents the OAuth authentication extension
|
||||
OAuthAuthenticationExtension
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -62,5 +62,20 @@ angular.module('portainer.app')
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.OAuthAuthenticationEnabled = function() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
service.extensions(false)
|
||||
.then(function onSuccess(extensions) {
|
||||
var extensionAvailable = _.find(extensions, { Id: 2, Enabled: true }) ? true : false;
|
||||
deferred.resolve(extensionAvailable);
|
||||
})
|
||||
.catch(function onError(err) {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<p>LDAP authentication</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div ng-if="oauthAuthenticationAvailable">
|
||||
<input type="radio" id="registry_auth" ng-model="settings.AuthenticationMethod" ng-value="3">
|
||||
<label for="registry_auth">
|
||||
<div class="boxselector_header">
|
||||
|
@ -47,6 +47,16 @@
|
|||
<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>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.app')
|
||||
.controller('SettingsAuthenticationController', ['$q', '$scope', 'Notifications', 'SettingsService', 'FileUploadService', 'TeamService',
|
||||
function ($q, $scope, Notifications, SettingsService, FileUploadService, TeamService) {
|
||||
.controller('SettingsAuthenticationController', ['$q', '$scope', '$state', 'Notifications', 'SettingsService', 'FileUploadService', 'TeamService', 'ExtensionService',
|
||||
function($q, $scope, $state, Notifications, SettingsService, FileUploadService, TeamService, ExtensionService) {
|
||||
|
||||
$scope.state = {
|
||||
successfulConnectivityCheck: false,
|
||||
|
@ -14,6 +14,10 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService, TeamSer
|
|||
TLSCACert: ''
|
||||
};
|
||||
|
||||
$scope.goToOAuthExtensionView = function() {
|
||||
$state.go('portainer.extensions.extension', { id: 2 });
|
||||
};
|
||||
|
||||
$scope.isOauthEnabled = function isOauthEnabled() {
|
||||
return $scope.settings && $scope.settings.AuthenticationMethod === 3;
|
||||
};
|
||||
|
@ -25,7 +29,7 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService, TeamSer
|
|||
$scope.removeSearchConfiguration = function(index) {
|
||||
$scope.LDAPSettings.SearchSettings.splice(index, 1);
|
||||
};
|
||||
|
||||
|
||||
$scope.addGroupSearchConfiguration = function() {
|
||||
$scope.LDAPSettings.GroupSearchSettings.push({ GroupBaseDN: '', GroupAttribute: '', GroupFilter: '' });
|
||||
};
|
||||
|
@ -98,14 +102,17 @@ function ($q, $scope, Notifications, SettingsService, FileUploadService, TeamSer
|
|||
function initView() {
|
||||
$q.all({
|
||||
settings: SettingsService.settings(),
|
||||
teams: TeamService.teams()
|
||||
}).then(function success(data) {
|
||||
teams: TeamService.teams(),
|
||||
oauthAuthentication: ExtensionService.OAuthAuthenticationEnabled()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var settings = data.settings;
|
||||
$scope.teams = data.teams;
|
||||
$scope.settings = settings;
|
||||
$scope.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