mirror of
https://github.com/portainer/portainer.git
synced 2025-08-09 15:55:23 +02:00
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 providers
This commit is contained in:
parent
148ccd1bc4
commit
00f4fe0039
21 changed files with 201 additions and 135 deletions
|
@ -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';
|
||||
|
|
1
app/portainer/oauth/__module.js
Normal file
1
app/portainer/oauth/__module.js
Normal file
|
@ -0,0 +1 @@
|
|||
angular.module('portainer.oauth', ['ngResource']).constant('API_ENDPOINT_OAUTH', 'api/auth/oauth');
|
|
@ -0,0 +1,72 @@
|
|||
angular.module('portainer.oauth').controller('OAuthProviderSelectorController', function OAuthProviderSelectorController() {
|
||||
var ctrl = this;
|
||||
|
||||
this.providers = [
|
||||
{
|
||||
authUrl: 'https://login.microsoftonline.com/TENANT_ID/oauth2/authorize',
|
||||
accessTokenUrl: 'https://login.microsoftonline.com/TENANT_ID/oauth2/token',
|
||||
resourceUrl: 'https://graph.windows.net/TENANT_ID/me?api-version=2013-11-08',
|
||||
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',
|
||||
accessTokenUrl: 'https://accounts.google.com/o/oauth2/token',
|
||||
resourceUrl: 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json',
|
||||
userIdentifier: 'email',
|
||||
scopes: 'profile email',
|
||||
name: 'google',
|
||||
label: 'Google',
|
||||
description: 'Google OAuth provider',
|
||||
icon: 'fab fa-google',
|
||||
},
|
||||
{
|
||||
authUrl: 'https://github.com/login/oauth/authorize',
|
||||
accessTokenUrl: 'https://github.com/login/oauth/access_token',
|
||||
resourceUrl: 'https://api.github.com/user',
|
||||
userIdentifier: 'login',
|
||||
scopes: 'id email name',
|
||||
name: 'github',
|
||||
label: 'Github',
|
||||
description: 'Github OAuth provider',
|
||||
icon: 'fab fa-github',
|
||||
},
|
||||
{
|
||||
authUrl: '',
|
||||
accessTokenUrl: '',
|
||||
resourceUrl: '',
|
||||
userIdentifier: '',
|
||||
scopes: '',
|
||||
name: 'custom',
|
||||
label: 'Custom',
|
||||
description: 'Custom OAuth provider',
|
||||
icon: 'fa fa-user-check',
|
||||
},
|
||||
];
|
||||
|
||||
this.$onInit = onInit;
|
||||
|
||||
function onInit() {
|
||||
if (ctrl.provider.authUrl) {
|
||||
ctrl.provider = getProviderByURL(ctrl.provider.authUrl);
|
||||
} else {
|
||||
ctrl.provider = ctrl.providers[0];
|
||||
}
|
||||
ctrl.onSelect(ctrl.provider, false);
|
||||
}
|
||||
|
||||
function getProviderByURL(providerAuthURL) {
|
||||
if (providerAuthURL.indexOf('login.microsoftonline.com') !== -1) {
|
||||
return ctrl.providers[0];
|
||||
} else if (providerAuthURL.indexOf('accounts.google.com') !== -1) {
|
||||
return ctrl.providers[1];
|
||||
} else if (providerAuthURL.indexOf('github.com') !== -1) {
|
||||
return ctrl.providers[2];
|
||||
}
|
||||
return ctrl.providers[3];
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -0,0 +1,8 @@
|
|||
angular.module('portainer.oauth').component('oauthProvidersSelector', {
|
||||
templateUrl: './oauth-providers-selector.html',
|
||||
bindings: {
|
||||
onSelect: '<',
|
||||
provider: '=',
|
||||
},
|
||||
controller: 'OAuthProviderSelectorController',
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.oauth').controller('OAuthSettingsController', function OAuthSettingsController() {
|
||||
var ctrl = this;
|
||||
|
||||
this.state = {
|
||||
provider: {},
|
||||
overrideConfiguration: false,
|
||||
microsoftTenantID: '',
|
||||
};
|
||||
|
||||
this.$onInit = onInit;
|
||||
this.onSelectProvider = onSelectProvider;
|
||||
this.onMicrosoftTenantIDChange = onMicrosoftTenantIDChange;
|
||||
this.useDefaultProviderConfiguration = useDefaultProviderConfiguration;
|
||||
|
||||
function onMicrosoftTenantIDChange() {
|
||||
var tenantID = ctrl.state.microsoftTenantID;
|
||||
|
||||
ctrl.settings.AuthorizationURI = _.replace('https://login.microsoftonline.com/TENANT_ID/oauth2/authorize', 'TENANT_ID', tenantID);
|
||||
ctrl.settings.AccessTokenURI = _.replace('https://login.microsoftonline.com/TENANT_ID/oauth2/token', 'TENANT_ID', tenantID);
|
||||
ctrl.settings.ResourceURI = _.replace('https://graph.windows.net/TENANT_ID/me?api-version=2013-11-08', 'TENANT_ID', tenantID);
|
||||
}
|
||||
|
||||
function useDefaultProviderConfiguration() {
|
||||
ctrl.settings.AuthorizationURI = ctrl.state.provider.authUrl;
|
||||
ctrl.settings.AccessTokenURI = ctrl.state.provider.accessTokenUrl;
|
||||
ctrl.settings.ResourceURI = ctrl.state.provider.resourceUrl;
|
||||
ctrl.settings.UserIdentifier = ctrl.state.provider.userIdentifier;
|
||||
ctrl.settings.Scopes = ctrl.state.provider.scopes;
|
||||
|
||||
if (ctrl.state.provider.name === 'microsoft' && ctrl.state.microsoftTenantID !== '') {
|
||||
onMicrosoftTenantIDChange();
|
||||
}
|
||||
}
|
||||
|
||||
function useExistingConfiguration() {
|
||||
var provider = ctrl.state.provider;
|
||||
ctrl.settings.AuthorizationURI = ctrl.settings.AuthorizationURI === '' ? provider.authUrl : ctrl.settings.AuthorizationURI;
|
||||
ctrl.settings.AccessTokenURI = ctrl.settings.AccessTokenURI === '' ? provider.accessTokenUrl : ctrl.settings.AccessTokenURI;
|
||||
ctrl.settings.ResourceURI = ctrl.settings.ResourceURI === '' ? provider.resourceUrl : ctrl.settings.ResourceURI;
|
||||
ctrl.settings.UserIdentifier = ctrl.settings.UserIdentifier === '' ? provider.userIdentifier : ctrl.settings.UserIdentifier;
|
||||
ctrl.settings.Scopes = ctrl.settings.Scopes === '' ? provider.scopes : ctrl.settings.Scopes;
|
||||
|
||||
if (provider.name === 'microsoft' && ctrl.state.microsoftTenantID !== '') {
|
||||
onMicrosoftTenantIDChange();
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectProvider(provider, overrideConfiguration) {
|
||||
ctrl.state.provider = provider;
|
||||
|
||||
if (overrideConfiguration) {
|
||||
useDefaultProviderConfiguration();
|
||||
} else {
|
||||
useExistingConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
if (ctrl.settings.RedirectURI === '') {
|
||||
ctrl.settings.RedirectURI = window.location.origin;
|
||||
}
|
||||
|
||||
if (ctrl.settings.AuthorizationURI !== '') {
|
||||
ctrl.state.provider.authUrl = ctrl.settings.AuthorizationURI;
|
||||
|
||||
if (ctrl.settings.AuthorizationURI.indexOf('login.microsoftonline.com') > -1) {
|
||||
var tenantID = ctrl.settings.AuthorizationURI.match(/login.microsoftonline.com\/(.*?)\//)[1];
|
||||
ctrl.state.microsoftTenantID = tenantID;
|
||||
onMicrosoftTenantIDChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,164 @@
|
|||
<div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Automatic user provisioning
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
With automatic user provisioning enabled, Portainer will create user(s) automatically with the standard user role. If disabled, users must be created beforehand in Portainer
|
||||
in order to login.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left">Automatic user provisioning</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.settings.OAuthAutoCreateUsers" /><i></i> </label>
|
||||
</div>
|
||||
|
||||
<div ng-if="$ctrl.settings.OAuthAutoCreateUsers">
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
<p>The users created by the automatic provisioning feature can be added to a default team on creation.</p>
|
||||
<p
|
||||
>By assigning newly created users to a team, they will be able to access the environments associated to that team. This setting is optional and if not set, newly created
|
||||
users won't be able to access any environments.</p
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left">Default team</label>
|
||||
<span class="small text-muted" style="margin-left: 20px;" ng-if="$ctrl.teams.length === 0">
|
||||
You have not yet created any teams. Head over to the <a ui-sref="portainer.teams">Teams view</a> to manage teams.
|
||||
</span>
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.settings.DefaultTeamID = null" ng-disabled="!$ctrl.settings.DefaultTeamID" ng-if="$ctrl.teams.length > 0"
|
||||
><i class="fa fa-times" aria-hidden="true"></i
|
||||
></button>
|
||||
<div class="col-sm-9 col-lg-9" ng-if="$ctrl.teams.length > 0">
|
||||
<select class="form-control" ng-model="$ctrl.settings.DefaultTeamID" ng-options="team.Id as team.Name for team in $ctrl.teams">
|
||||
<option value="">No team</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<oauth-providers-selector on-select="($ctrl.onSelectProvider)" provider="$ctrl.state.provider"></oauth-providers-selector>
|
||||
|
||||
<div class="col-sm-12 form-section-title">OAuth Configuration</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.state.provider.name == 'microsoft'">
|
||||
<label for="oauth_microsoft_tenant_id" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Tenant ID
|
||||
<portainer-tooltip position="bottom" message="ID of the Azure Directory you wish to authenticate against. Also known as the Directory ID"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="oauth_microsoft_tenant_id"
|
||||
placeholder="xxxxxxxxxxxxxxxxxxxx"
|
||||
ng-model="$ctrl.state.microsoftTenantID"
|
||||
ng-change="$ctrl.onMicrosoftTenantIDChange()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="oauth_client_id" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
{{ $ctrl.state.provider.name == 'microsoft' ? 'Application ID' : 'Client ID' }}
|
||||
<portainer-tooltip position="bottom" message="Public identifier of the OAuth application"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="oauth_client_id" ng-model="$ctrl.settings.ClientID" placeholder="xxxxxxxxxxxxxxxxxxxx" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="oauth_client_secret" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
{{ $ctrl.state.provider.name == 'microsoft' ? 'Application key' : 'Client secret' }}
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="password" class="form-control" id="oauth_client_secret" ng-model="$ctrl.settings.ClientSecret" placeholder="xxxxxxxxxxxxxxxxxxxx" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.state.provider.name == 'custom' || $ctrl.state.overrideConfiguration">
|
||||
<label for="oauth_authorization_uri" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Authorization URL
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="URL used to authenticate against the OAuth provider. Will redirect the user to the OAuth provider login view"
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="oauth_authorization_uri" ng-model="$ctrl.settings.AuthorizationURI" placeholder="https://example.com/oauth/authorize" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.state.provider.name == 'custom' || $ctrl.state.overrideConfiguration">
|
||||
<label for="oauth_access_token_uri" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Access token URL
|
||||
<portainer-tooltip position="bottom" message="URL used by Portainer to exchange a valid OAuth authentication code for an access token"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="oauth_access_token_uri" ng-model="$ctrl.settings.AccessTokenURI" placeholder="https://example.com/oauth/token" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.state.provider.name == 'custom' || $ctrl.state.overrideConfiguration">
|
||||
<label for="oauth_resource_uri" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Resource URL
|
||||
<portainer-tooltip position="bottom" message="URL used by Portainer to retrieve information about the authenticated user"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="oauth_resource_uri" ng-model="$ctrl.settings.ResourceURI" placeholder="https://example.com/user" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.state.provider.name == 'custom' || $ctrl.state.overrideConfiguration">
|
||||
<label for="oauth_redirect_uri" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Redirect URL
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="URL used by the OAuth provider to redirect the user after successful authentication. Should be set to your Portainer instance URL"
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="oauth_redirect_uri" ng-model="$ctrl.settings.RedirectURI" placeholder="http://yourportainer.com/" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.state.provider.name == 'custom' || $ctrl.state.overrideConfiguration">
|
||||
<label for="oauth_user_identifier" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
User identifier
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="Identifier that will be used by Portainer to create an account for the authenticated user. Retrieved from the resource server specified via the Resource URL field"
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="oauth_user_identifier" ng-model="$ctrl.settings.UserIdentifier" placeholder="id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.state.provider.name == 'custom' || $ctrl.state.overrideConfiguration">
|
||||
<label for="oauth_scopes" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Scopes
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="Scopes required by the OAuth provider to retrieve information about the authenticated user. Refer to your OAuth provider documentation for more information about this"
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="oauth_scopes" ng-model="$ctrl.settings.Scopes" placeholder="id,email,name" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.state.provider.name != 'custom'">
|
||||
<div class="col-sm-12">
|
||||
<a class="small interactive" ng-if="!$ctrl.state.overrideConfiguration" ng-click="$ctrl.state.overrideConfiguration = true;">
|
||||
<i class="fa fa-wrench space-right" aria-hidden="true"></i> Override default configuration
|
||||
</a>
|
||||
<a class="small interactive" ng-if="$ctrl.state.overrideConfiguration" ng-click="$ctrl.state.overrideConfiguration = false; $ctrl.useDefaultProviderConfiguration()">
|
||||
<i class="fa fa-cogs space-right" aria-hidden="true"></i> Use default configuration
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
angular.module('portainer.oauth').component('oauthSettings', {
|
||||
templateUrl: './oauth-settings.html',
|
||||
bindings: {
|
||||
settings: '=',
|
||||
teams: '<',
|
||||
},
|
||||
controller: 'OAuthSettingsController',
|
||||
});
|
20
app/portainer/oauth/services/rest/oauth.js
Normal file
20
app/portainer/oauth/services/rest/oauth.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
angular.module('portainer.oauth').factory('OAuth', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_OAUTH',
|
||||
function OAuthFactory($resource, API_ENDPOINT_OAUTH) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_OAUTH + '/:action',
|
||||
{},
|
||||
{
|
||||
validate: {
|
||||
method: 'POST',
|
||||
ignoreLoadingBar: true,
|
||||
params: {
|
||||
action: 'validate',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -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…
Add table
Add a link
Reference in a new issue