Merge pull request #2393 from coreos-inc/oidc-ui
OIDC configuration support in superuser config panel
This commit is contained in:
commit
6d6be63ca6
8 changed files with 363 additions and 129 deletions
|
@ -12,7 +12,7 @@ PREFIX_BLACKLIST = ['ldap', 'jwt', 'keystone']
|
||||||
|
|
||||||
class OAuthLoginManager(object):
|
class OAuthLoginManager(object):
|
||||||
""" Helper class which manages all registered OAuth login services. """
|
""" Helper class which manages all registered OAuth login services. """
|
||||||
def __init__(self, config):
|
def __init__(self, config, client=None):
|
||||||
self.services = []
|
self.services = []
|
||||||
|
|
||||||
# Register the endpoints for each of the OAuth login services.
|
# Register the endpoints for each of the OAuth login services.
|
||||||
|
@ -28,7 +28,7 @@ class OAuthLoginManager(object):
|
||||||
if prefix in PREFIX_BLACKLIST:
|
if prefix in PREFIX_BLACKLIST:
|
||||||
raise Exception('Cannot use reserved config name %s' % key)
|
raise Exception('Cannot use reserved config name %s' % key)
|
||||||
|
|
||||||
self.services.append(OIDCLoginService(config, key))
|
self.services.append(OIDCLoginService(config, key, client=client))
|
||||||
|
|
||||||
def get_service(self, service_id):
|
def get_service(self, service_id):
|
||||||
for service in self.services:
|
for service in self.services:
|
||||||
|
|
|
@ -34,12 +34,12 @@ class PublicKeyLoadException(Exception):
|
||||||
|
|
||||||
class OIDCLoginService(OAuthService):
|
class OIDCLoginService(OAuthService):
|
||||||
""" Defines a generic service for all OpenID-connect compatible login services. """
|
""" Defines a generic service for all OpenID-connect compatible login services. """
|
||||||
def __init__(self, config, key_name):
|
def __init__(self, config, key_name, client=None):
|
||||||
super(OIDCLoginService, self).__init__(config, key_name)
|
super(OIDCLoginService, self).__init__(config, key_name)
|
||||||
|
|
||||||
self._public_key_cache = TTLCache(1, PUBLIC_KEY_CACHE_TTL, missing=self._load_public_key)
|
self._public_key_cache = TTLCache(1, PUBLIC_KEY_CACHE_TTL, missing=self._load_public_key)
|
||||||
self._id = key_name[0:key_name.find('_')].lower()
|
self._id = key_name[0:key_name.find('_')].lower()
|
||||||
self._http_client = config['HTTPCLIENT']
|
self._http_client = client or config['HTTPCLIENT']
|
||||||
self._mailing = config.get('FEATURE_MAILING', False)
|
self._mailing = config.get('FEATURE_MAILING', False)
|
||||||
|
|
||||||
def service_id(self):
|
def service_id(self):
|
||||||
|
@ -71,6 +71,9 @@ class OIDCLoginService(OAuthService):
|
||||||
def user_endpoint(self):
|
def user_endpoint(self):
|
||||||
return self._oidc_config().get('userinfo_endpoint')
|
return self._oidc_config().get('userinfo_endpoint')
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
return bool(self.user_endpoint())
|
||||||
|
|
||||||
def validate_client_id_and_secret(self, http_client, app_config):
|
def validate_client_id_and_secret(self, http_client, app_config):
|
||||||
# TODO: find a way to verify client secret too.
|
# TODO: find a way to verify client secret too.
|
||||||
check_auth_url = http_client.get(self.get_auth_url())
|
check_auth_url = http_client.get(self.get_auth_url())
|
||||||
|
|
|
@ -431,6 +431,18 @@ a:focus {
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.co-panel-body .co-panel-heading {
|
||||||
|
font-size: 120%;
|
||||||
|
border-bottom: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.co-panel-body .co-panel-body {
|
||||||
|
padding-left: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.config-bool-field-element input {
|
.config-bool-field-element input {
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
|
|
@ -525,17 +525,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- /E-mail -->
|
</div> <!-- /E-mail -->
|
||||||
|
|
||||||
<!-- Authentication -->
|
<!-- Internal Authentication -->
|
||||||
<div class="co-panel">
|
<div class="co-panel">
|
||||||
<div class="co-panel-heading">
|
<div class="co-panel-heading">
|
||||||
<i class="fa fa-users"></i> Authentication
|
<i class="fa fa-users"></i> Internal Authentication
|
||||||
</div>
|
</div>
|
||||||
<div class="co-panel-body">
|
<div class="co-panel-body">
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<p>
|
<p>
|
||||||
Authentication for the registry can be handled by either the registry itself, LDAP or external JWT endpoint.
|
Authentication for the registry can be handled by either the registry itself, LDAP or external JWT endpoint.
|
||||||
<br>
|
</p>
|
||||||
Additional external authentication providers (such as GitHub) can be used on top of this choice.
|
<p>
|
||||||
|
Additional <strong>external</strong> authentication providers (such as GitHub) can be used in addition for <strong>login into the UI</strong>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -807,134 +808,228 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- /Authentication -->
|
</div> <!-- / Internal Authentication -->
|
||||||
|
|
||||||
<!-- GitHub Authentication -->
|
<div class="co-panel"> <!-- External Authentication -->
|
||||||
<div class="co-panel">
|
|
||||||
<div class="co-panel-heading">
|
<div class="co-panel-heading">
|
||||||
<i class="fa fa-github"></i> GitHub (Enterprise) Authentication
|
<i class="fa fa-id-card"></i> External Authorization (OAuth)
|
||||||
</div>
|
</div>
|
||||||
<div class="co-panel-body">
|
<div class="co-panel-body">
|
||||||
<div class="description">
|
<!-- GitHub Authentication -->
|
||||||
<p>
|
<div class="co-panel">
|
||||||
If enabled, users can use GitHub or GitHub Enterprise to authenticate to the registry.
|
<div class="co-panel-heading">
|
||||||
</p>
|
<i class="fa fa-github"></i> GitHub (Enterprise) Authentication
|
||||||
<p>
|
</div>
|
||||||
<strong>Note:</strong> A registered GitHub (Enterprise) OAuth application is required.
|
<div class="co-panel-body">
|
||||||
View instructions on how to
|
<div class="description">
|
||||||
<a href="https://coreos.com/docs/enterprise-registry/github-app/" ng-safenewtab>
|
<p>
|
||||||
Create an OAuth Application in GitHub
|
If enabled, users can use GitHub or GitHub Enterprise to authenticate to the registry.
|
||||||
</a>
|
</p>
|
||||||
</p>
|
<p>
|
||||||
|
<strong>Note:</strong> A registered GitHub (Enterprise) OAuth application is required.
|
||||||
|
View instructions on how to
|
||||||
|
<a href="https://coreos.com/docs/enterprise-registry/github-app/" ng-safenewtab>
|
||||||
|
Create an OAuth Application in GitHub
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-bool-field" binding="config.FEATURE_GITHUB_LOGIN">
|
||||||
|
Enable GitHub Authentication
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="config-table" ng-if="config.FEATURE_GITHUB_LOGIN">
|
||||||
|
<tr>
|
||||||
|
<td>GitHub:</td>
|
||||||
|
<td>
|
||||||
|
<select class="form-control" ng-model="mapped.GITHUB_LOGIN_KIND">
|
||||||
|
<option value="hosted">GitHub.com</option>
|
||||||
|
<option value="enterprise">GitHub Enterprise</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-if="mapped.GITHUB_LOGIN_KIND == 'enterprise'">
|
||||||
|
<td>GitHub Endpoint:</td>
|
||||||
|
<td>
|
||||||
|
<span class="config-string-field"
|
||||||
|
binding="config.GITHUB_LOGIN_CONFIG.GITHUB_ENDPOINT"
|
||||||
|
placeholder="https://my.githubserver"
|
||||||
|
pattern="{{ GITHOST_REGEX }}">
|
||||||
|
</span>
|
||||||
|
<div class="help-text">
|
||||||
|
The GitHub Enterprise endpoint. Must start with http:// or https://.
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>OAuth Client ID:</td>
|
||||||
|
<td>
|
||||||
|
<span class="config-string-field" binding="config.GITHUB_LOGIN_CONFIG.CLIENT_ID">
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>OAuth Client Secret:</td>
|
||||||
|
<td>
|
||||||
|
<span class="config-string-field" binding="config.GITHUB_LOGIN_CONFIG.CLIENT_SECRET">
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Organization Filtering:</td>
|
||||||
|
<td>
|
||||||
|
<div class="config-bool-field" binding="config.GITHUB_LOGIN_CONFIG.ORG_RESTRICT">
|
||||||
|
Restrict By Organization Membership
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="help-text" style="margin-bottom: 20px;">
|
||||||
|
If enabled, only members of specified GitHub
|
||||||
|
<span ng-if="mapped.GITHUB_LOGIN_KIND == 'enterprise'">Enterprise</span> organizations will be allowed to login via GitHub
|
||||||
|
<span ng-if="mapped.GITHUB_LOGIN_KIND == 'enterprise'">Enterprise</span>.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="config-list-field"
|
||||||
|
item-title="Organization ID"
|
||||||
|
binding="config.GITHUB_LOGIN_CONFIG.ALLOWED_ORGANIZATIONS"
|
||||||
|
ng-if="config.GITHUB_LOGIN_CONFIG.ORG_RESTRICT">
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div> <!-- /GitHub Authentication -->
|
||||||
|
|
||||||
|
<!-- Google Authentication -->
|
||||||
|
<div class="co-panel">
|
||||||
|
<div class="co-panel-heading">
|
||||||
|
<i class="fa fa-google"></i> Google Authentication
|
||||||
|
</div>
|
||||||
|
<div class="co-panel-body">
|
||||||
|
<div class="description">
|
||||||
|
<p>
|
||||||
|
If enabled, users can use Google to authenticate to the registry.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Note:</strong> A registered Google OAuth application is required.
|
||||||
|
Visit the
|
||||||
|
<a href="https://console.developers.google.com" ng-safenewtab>
|
||||||
|
Google Developer Console
|
||||||
|
</a>
|
||||||
|
to register an application.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-bool-field" binding="config.FEATURE_GOOGLE_LOGIN">
|
||||||
|
Enable Google Authentication
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="config-table" ng-if="config.FEATURE_GOOGLE_LOGIN">
|
||||||
|
<tr>
|
||||||
|
<td>OAuth Client ID:</td>
|
||||||
|
<td>
|
||||||
|
<span class="config-string-field" binding="config.GOOGLE_LOGIN_CONFIG.CLIENT_ID">
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>OAuth Client Secret:</td>
|
||||||
|
<td>
|
||||||
|
<span class="config-string-field" binding="config.GOOGLE_LOGIN_CONFIG.CLIENT_SECRET">
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div> <!-- /Google Authentication -->
|
||||||
|
|
||||||
|
<!-- Custom OIDC providers -->
|
||||||
|
<div class="co-panel" ng-repeat="provider in getOIDCProviders(config)">
|
||||||
|
<div class="co-panel-heading">
|
||||||
|
<span class="icon-image-view" value="{{ config[provider]['SERVICE_ICON'] || 'fa-user-circle' }}" style="margin-right: 6px;"></span>
|
||||||
|
{{ config[provider]['SERVICE_NAME'] || (getOIDCProviderId(provider) + ' Authentication') }}
|
||||||
|
<span style="display: inline-block; margin-left: 10px">(<a href="javascript:void(0)" ng-click="removeOIDCProvider(provider)">Delete</a>)</span>
|
||||||
|
</div>
|
||||||
|
<div class="co-panel-body">
|
||||||
|
<div class="co-alert co-alert-warning" ng-if="config.AUTHENTICATION_TYPE != 'Database' && !(config[provider].LOGIN_BINDING_FIELD)">
|
||||||
|
Warning: This OIDC provider is not bound to your <strong>{{ config.AUTHENTICATION_TYPE }}</strong> authentication. Logging in via this provider will create a <strong><span class="registry-name"></span>-only user</strong>, which is not the recommended approach. It is <strong>highly</strong> recommended to choose a "Binding Field" below.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="config-table">
|
||||||
|
<tr>
|
||||||
|
<td class="non-input">Service ID:</td>
|
||||||
|
<td>
|
||||||
|
<code>{{ getOIDCProviderId(provider) }}</code>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>OIDC Server:</td>
|
||||||
|
<td>
|
||||||
|
<span class="config-string-field"
|
||||||
|
binding="config[provider].OIDC_SERVER"
|
||||||
|
placeholder="https://path/to/oidc/compliant/server"
|
||||||
|
pattern="https://.+">
|
||||||
|
</span>
|
||||||
|
<div class="help-text">
|
||||||
|
The URL of an OIDC-compliant server.
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Service Name:</td>
|
||||||
|
<td>
|
||||||
|
<span class="config-string-field"
|
||||||
|
binding="config[provider].SERVICE_NAME"
|
||||||
|
placeholder="My Authentication Service">
|
||||||
|
</span>
|
||||||
|
<div class="help-text">
|
||||||
|
The user friendly name to display for the service on the login page.
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Service Icon (optional):</td>
|
||||||
|
<td>
|
||||||
|
<span class="config-string-field"
|
||||||
|
binding="config[provider].SERVICE_ICON"
|
||||||
|
placeholder="URL of the icon to use for this service OR a font awesome CSS name"
|
||||||
|
is-optional="true">
|
||||||
|
</span>
|
||||||
|
<div class="help-text">
|
||||||
|
If specified, the icon to display for this login service on the login page. Can be either a URL to an icon or a CSS class name from <a href="http://fontawesome.io" ng-safenewtab>Font Awesome</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-if="config.AUTHENTICATION_TYPE != 'Database'">
|
||||||
|
<td>Binding Field:</td>
|
||||||
|
<td>
|
||||||
|
<select class="form-control" ng-model="config[provider].LOGIN_BINDING_FIELD">
|
||||||
|
<option value="">(None)</option>
|
||||||
|
<option value="sub">Subject (User ID)</option>
|
||||||
|
<option value="username">Username</option>
|
||||||
|
<option value="email">E-mail address</option>
|
||||||
|
</select>
|
||||||
|
<div class="help-text">
|
||||||
|
If selected, when a user logs in via this OIDC provider, they will be automatically bound to their user in <strong>{{ config.AUTHENTICATION_TYPE }}</strong> by matching the selected field from the OIDC provider to the associated user in {{ config.AUTHENTICATION_TYPE }}.
|
||||||
|
</div>
|
||||||
|
<div class="help-text">
|
||||||
|
For example, selecting <code>Subject</code> here with a backing authentication system of LDAP means that a user logging in via this OIDC provider will also be bound to their user in LDAP by username.
|
||||||
|
</div>
|
||||||
|
<div class="help-text">
|
||||||
|
If none selected, a <strong>user unique to <span class="registry-name"></span></strong> will be created on initial login with this OIDC provider. <strong>This is not the recommended setup.</strong>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="config-bool-field" binding="config.FEATURE_GITHUB_LOGIN">
|
<!-- Add Provider -->
|
||||||
Enable GitHub Authentication
|
<a class="btn btn-default" ng-click="addOIDCProvider()" style="margin-right: 6px;">Add OIDC Provider</a>
|
||||||
</div>
|
<a href="http://openid.net/connect/" ng-safenewtab>What is OIDC?</a>
|
||||||
|
|
||||||
<table class="config-table" ng-if="config.FEATURE_GITHUB_LOGIN">
|
|
||||||
<tr>
|
|
||||||
<td>GitHub:</td>
|
|
||||||
<td>
|
|
||||||
<select class="form-control" ng-model="mapped.GITHUB_LOGIN_KIND">
|
|
||||||
<option value="hosted">GitHub.com</option>
|
|
||||||
<option value="enterprise">GitHub Enterprise</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr ng-if="mapped.GITHUB_LOGIN_KIND == 'enterprise'">
|
|
||||||
<td>GitHub Endpoint:</td>
|
|
||||||
<td>
|
|
||||||
<span class="config-string-field"
|
|
||||||
binding="config.GITHUB_LOGIN_CONFIG.GITHUB_ENDPOINT"
|
|
||||||
placeholder="https://my.githubserver"
|
|
||||||
pattern="{{ GITHOST_REGEX }}">
|
|
||||||
</span>
|
|
||||||
<div class="help-text">
|
|
||||||
The GitHub Enterprise endpoint. Must start with http:// or https://.
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>OAuth Client ID:</td>
|
|
||||||
<td>
|
|
||||||
<span class="config-string-field" binding="config.GITHUB_LOGIN_CONFIG.CLIENT_ID">
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>OAuth Client Secret:</td>
|
|
||||||
<td>
|
|
||||||
<span class="config-string-field" binding="config.GITHUB_LOGIN_CONFIG.CLIENT_SECRET">
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Organization Filtering:</td>
|
|
||||||
<td>
|
|
||||||
<div class="config-bool-field" binding="config.GITHUB_LOGIN_CONFIG.ORG_RESTRICT">
|
|
||||||
Restrict By Organization Membership
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="help-text" style="margin-bottom: 20px;">
|
|
||||||
If enabled, only members of specified GitHub
|
|
||||||
<span ng-if="mapped.GITHUB_LOGIN_KIND == 'enterprise'">Enterprise</span> organizations will be allowed to login via GitHub
|
|
||||||
<span ng-if="mapped.GITHUB_LOGIN_KIND == 'enterprise'">Enterprise</span>.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="config-list-field"
|
|
||||||
item-title="Organization ID"
|
|
||||||
binding="config.GITHUB_LOGIN_CONFIG.ALLOWED_ORGANIZATIONS"
|
|
||||||
ng-if="config.GITHUB_LOGIN_CONFIG.ORG_RESTRICT">
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- /GitHub Authentication -->
|
</div> <!-- /External Authentication -->
|
||||||
|
|
||||||
<!-- Google Authentication -->
|
|
||||||
<div class="co-panel">
|
|
||||||
<div class="co-panel-heading">
|
|
||||||
<i class="fa fa-google"></i> Google Authentication
|
|
||||||
</div>
|
|
||||||
<div class="co-panel-body">
|
|
||||||
<div class="description">
|
|
||||||
<p>
|
|
||||||
If enabled, users can use Google to authenticate to the registry.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>Note:</strong> A registered Google OAuth application is required.
|
|
||||||
Visit the
|
|
||||||
<a href="https://console.developers.google.com" ng-safenewtab>
|
|
||||||
Google Developer Console
|
|
||||||
</a>
|
|
||||||
to register an application.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="config-bool-field" binding="config.FEATURE_GOOGLE_LOGIN">
|
|
||||||
Enable Google Authentication
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="config-table" ng-if="config.FEATURE_GOOGLE_LOGIN">
|
|
||||||
<tr>
|
|
||||||
<td>OAuth Client ID:</td>
|
|
||||||
<td>
|
|
||||||
<span class="config-string-field" binding="config.GOOGLE_LOGIN_CONFIG.CLIENT_ID">
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>OAuth Client Secret:</td>
|
|
||||||
<td>
|
|
||||||
<span class="config-string-field" binding="config.GOOGLE_LOGIN_CONFIG.CLIENT_SECRET">
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div> <!-- /Google Authentication -->
|
|
||||||
|
|
||||||
<!-- Build Support -->
|
<!-- Build Support -->
|
||||||
<div class="co-panel">
|
<div class="co-panel">
|
||||||
|
|
|
@ -71,7 +71,11 @@ angular.module("core-config-setup", ['angularFileUpload'])
|
||||||
|
|
||||||
{'id': 'bittorrent', 'title': 'BitTorrent downloads', 'condition': function(config) {
|
{'id': 'bittorrent', 'title': 'BitTorrent downloads', 'condition': function(config) {
|
||||||
return config.FEATURE_BITTORRENT;
|
return config.FEATURE_BITTORRENT;
|
||||||
}}
|
}},
|
||||||
|
|
||||||
|
{'id': 'oidc-login', 'title': 'OIDC Login(s)', 'condition': function(config) {
|
||||||
|
return $scope.getOIDCProviders(config).length > 0;
|
||||||
|
}},
|
||||||
];
|
];
|
||||||
|
|
||||||
$scope.STORAGE_CONFIG_FIELDS = {
|
$scope.STORAGE_CONFIG_FIELDS = {
|
||||||
|
@ -147,6 +151,59 @@ angular.module("core-config-setup", ['angularFileUpload'])
|
||||||
$scope.validating = null;
|
$scope.validating = null;
|
||||||
$scope.savingConfiguration = false;
|
$scope.savingConfiguration = false;
|
||||||
|
|
||||||
|
$scope.removeOIDCProvider = function(provider) {
|
||||||
|
delete $scope.config[provider];
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addOIDCProvider = function() {
|
||||||
|
bootbox.prompt('Enter an ID for the OIDC provider', function(result) {
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.toUpperCase();
|
||||||
|
|
||||||
|
if (!result.match(/^[A-Z0-9]+$/)) {
|
||||||
|
bootbox.alert('Invalid ID for OIDC provider: must be alphanumeric');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == 'GITHUB' || result == 'GOOGLE') {
|
||||||
|
bootbox.alert('Invalid ID for OIDC provider: cannot be a reserved name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = result + '_LOGIN_CONFIG';
|
||||||
|
if ($scope.config[key]) {
|
||||||
|
bootbox.alert('Invalid ID for OIDC provider: already exists');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.config[key] = {};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getOIDCProviderId = function(key) {
|
||||||
|
var index = key.indexOf('_LOGIN_CONFIG');
|
||||||
|
if (index <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return key.substr(0, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getOIDCProviders = function(config) {
|
||||||
|
var keys = Object.keys(config || {});
|
||||||
|
return keys.filter(function(key) {
|
||||||
|
if (key == 'GITHUB_LOGIN_CONFIG' || key == 'GOOGLE_LOGIN_CONFIG') {
|
||||||
|
// Has custom UI and config.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!$scope.getOIDCProviderId(key);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.getServices = function(config) {
|
$scope.getServices = function(config) {
|
||||||
var services = [];
|
var services = [];
|
||||||
if (!config) { return services; }
|
if (!config) { return services; }
|
||||||
|
|
|
@ -18,6 +18,7 @@ from util.config.validators.validate_google_login import GoogleLoginValidator
|
||||||
from util.config.validators.validate_bitbucket_trigger import BitbucketTriggerValidator
|
from util.config.validators.validate_bitbucket_trigger import BitbucketTriggerValidator
|
||||||
from util.config.validators.validate_gitlab_trigger import GitLabTriggerValidator
|
from util.config.validators.validate_gitlab_trigger import GitLabTriggerValidator
|
||||||
from util.config.validators.validate_github import GitHubLoginValidator, GitHubTriggerValidator
|
from util.config.validators.validate_github import GitHubLoginValidator, GitHubTriggerValidator
|
||||||
|
from util.config.validators.validate_oidc import OIDCLoginValidator
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ VALIDATORS = {
|
||||||
SignerValidator.name: SignerValidator.validate,
|
SignerValidator.name: SignerValidator.validate,
|
||||||
SecurityScannerValidator.name: SecurityScannerValidator.validate,
|
SecurityScannerValidator.name: SecurityScannerValidator.validate,
|
||||||
BittorrentValidator.name: BittorrentValidator.validate,
|
BittorrentValidator.name: BittorrentValidator.validate,
|
||||||
|
OIDCLoginValidator.name: OIDCLoginValidator.validate,
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_service_for_config(service, config, password=None):
|
def validate_service_for_config(service, config, password=None):
|
||||||
|
|
38
util/config/validators/test/test_validate_oidc.py
Normal file
38
util/config/validators/test/test_validate_oidc.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import json
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from httmock import urlmatch, HTTMock
|
||||||
|
|
||||||
|
from oauth.oidc import OIDC_WELLKNOWN
|
||||||
|
from util.config.validators import ConfigValidationException
|
||||||
|
from util.config.validators.validate_oidc import OIDCLoginValidator
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('unvalidated_config', [
|
||||||
|
({'SOMETHING_LOGIN_CONFIG': {}}),
|
||||||
|
])
|
||||||
|
def test_validate_invalid_oidc_login_config(unvalidated_config):
|
||||||
|
validator = OIDCLoginValidator()
|
||||||
|
|
||||||
|
with pytest.raises(ConfigValidationException):
|
||||||
|
validator.validate(unvalidated_config, None, None)
|
||||||
|
|
||||||
|
def test_validate_oidc_login():
|
||||||
|
url_hit = [False]
|
||||||
|
@urlmatch(netloc=r'someserver', path=r'/\.well-known/openid-configuration')
|
||||||
|
def handler(_, __):
|
||||||
|
url_hit[0] = True
|
||||||
|
data = {
|
||||||
|
'userinfo_endpoint': 'foobar',
|
||||||
|
}
|
||||||
|
return {'status_code': 200, 'content': json.dumps(data)}
|
||||||
|
|
||||||
|
with HTTMock(handler):
|
||||||
|
validator = OIDCLoginValidator()
|
||||||
|
validator.validate({
|
||||||
|
'SOMETHING_LOGIN_CONFIG': {
|
||||||
|
'OIDC_SERVER': 'http://someserver',
|
||||||
|
'DEBUGGING': True, # Allows for HTTP.
|
||||||
|
},
|
||||||
|
}, None, None)
|
||||||
|
|
||||||
|
assert url_hit[0]
|
27
util/config/validators/validate_oidc.py
Normal file
27
util/config/validators/validate_oidc.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from app import app
|
||||||
|
from oauth.loginmanager import OAuthLoginManager
|
||||||
|
from oauth.oidc import OIDCLoginService, DiscoveryFailureException
|
||||||
|
from util.config.validators import BaseValidator, ConfigValidationException
|
||||||
|
|
||||||
|
class OIDCLoginValidator(BaseValidator):
|
||||||
|
name = "oidc-login"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, config, user, user_password):
|
||||||
|
client = app.config['HTTPCLIENT']
|
||||||
|
login_manager = OAuthLoginManager(config, client=client)
|
||||||
|
for service in login_manager.services:
|
||||||
|
if not isinstance(service, OIDCLoginService):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if service.config.get('OIDC_SERVER') is None:
|
||||||
|
msg = 'Missing OIDC_SERVER on OIDC service %s' % service.service_id()
|
||||||
|
raise ConfigValidationException(msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not service.validate():
|
||||||
|
msg = 'Could not validate OIDC service %s' % service.service_id()
|
||||||
|
raise ConfigValidationException(msg)
|
||||||
|
except DiscoveryFailureException as dfe:
|
||||||
|
msg = 'Could not validate OIDC service %s: %s' % (service.service_id(), dfe.message)
|
||||||
|
raise ConfigValidationException(msg)
|
Reference in a new issue