diff --git a/static/directives/config/config-setup-tool.html b/static/directives/config/config-setup-tool.html
index 04ac2abf2..22e302ca5 100644
--- a/static/directives/config/config-setup-tool.html
+++ b/static/directives/config/config-setup-tool.html
@@ -411,7 +411,7 @@
+ pattern="{{ GITHOST_REGEX }}">
The GitHub Enterprise endpoint. Must start with http:// or https://.
@@ -524,7 +524,6 @@
-
@@ -565,7 +564,7 @@
+ pattern="{{ GITHOST_REGEX }}">
The GitHub Enterprise endpoint. Must start with http:// or https://.
@@ -589,6 +588,115 @@
+
+
+
+
+ BitBucket Build Triggers
+
+
+
+
+ If enabled, users can setup BitBucket triggers to invoke Registry builds.
+
+
+ Note: A registered BitBucket OAuth application is required.
+ View instructions on how to
+
+ Create an OAuth Application in BitBucket
+
+
+
+
+
+
+
+
+
+
+
+ OAuth Consumer Key: |
+
+
+
+ |
+
+
+ OAuth Consumer Secret: |
+
+
+
+ |
+
+
+
+
+
+
+
+
+ GitLab Build Triggers
+
+
+
+
+ If enabled, users can setup GitLab triggers to invoke Registry builds.
+
+
+ Note: A registered GitLab OAuth application is required.
+ View instructions on how to
+
+ Create an OAuth Application in GitLab
+
+
+
+
+
+
+
+
+
+
+
+ GitLab: |
+
+
+ |
+
+
+ GitLab Endpoint: |
+
+
+
+
+ The GitLab Enterprise endpoint. Must start with http:// or https://.
+
+ |
+
+
+ OAuth Client ID: |
+
+
+
+ |
+
+
+ OAuth Client Secret: |
+
+
+
+ |
+
+
+
+
+
diff --git a/static/js/core-config-setup.js b/static/js/core-config-setup.js
index 569169201..033bbfbb6 100644
--- a/static/js/core-config-setup.js
+++ b/static/js/core-config-setup.js
@@ -12,7 +12,7 @@ angular.module("core-config-setup", ['angularFileUpload'])
},
controller: function($rootScope, $scope, $element, $timeout, ApiService) {
$scope.HOSTNAME_REGEX = '^[a-zA-Z-0-9\.]+(:[0-9]+)?$';
- $scope.GITHUB_REGEX = '^https?://([a-zA-Z0-9]+\.?\/?)+$';
+ $scope.GITHOST_REGEX = '^https?://([a-zA-Z0-9]+\.?\/?)+$';
$scope.SERVICES = [
{'id': 'redis', 'title': 'Redis'},
@@ -39,8 +39,16 @@ angular.module("core-config-setup", ['angularFileUpload'])
return config.FEATURE_GOOGLE_LOGIN;
}},
- {'id': 'github-trigger', 'title': 'Github (Enterprise) Build Triggers', 'condition': function(config) {
+ {'id': 'github-trigger', 'title': 'GitHub (Enterprise) Build Triggers', 'condition': function(config) {
return config.FEATURE_GITHUB_BUILD;
+ }},
+
+ {'id': 'bitbucket-trigger', 'title': 'BitBucket Build Triggers', 'condition': function(config) {
+ return config.FEATURE_BITBUCKET_BUILD;
+ }},
+
+ {'id': 'gitlab-trigger', 'title': 'GitLab Build Triggers', 'condition': function(config) {
+ return config.FEATURE_GITLAB_BUILD;
}}
];
@@ -184,6 +192,24 @@ angular.module("core-config-setup", ['angularFileUpload'])
}, ApiService.errorDisplay('Could not save configuration. Please report this error.'));
};
+ var gitlabSelector = function(key) {
+ return function(value) {
+ if (!value || !$scope.config) { return; }
+
+ if (!$scope.config[key]) {
+ $scope.config[key] = {};
+ }
+
+ if (value == 'enterprise') {
+ if ($scope.config[key]['GITLAB_ENDPOINT'] == 'https://gitlab.com/') {
+ $scope.config[key]['GITLAB_ENDPOINT'] = '';
+ }
+ } else if (value == 'hosted') {
+ $scope.config[key]['GITLAB_ENDPOINT'] = 'https://gitlab.com/';
+ }
+ };
+ };
+
var githubSelector = function(key) {
return function(value) {
if (!value || !$scope.config) { return; }
@@ -226,6 +252,9 @@ angular.module("core-config-setup", ['angularFileUpload'])
$scope.mapped['GITHUB_LOGIN_KIND'] = gle == 'https://github.com/' ? 'hosted' : 'enterprise';
$scope.mapped['GITHUB_TRIGGER_KIND'] = gte == 'https://github.com/' ? 'hosted' : 'enterprise';
+ var glabe = getKey(config, 'GITLAB_TRIGGER_KIND.GITHUB_ENDPOINT');
+ $scope.mapped['GITLAB_TRIGGER_KIND'] = glabe == 'https://gitlab.com/' ? 'hosted' : 'enterprise';
+
$scope.mapped['redis'] = {};
$scope.mapped['redis']['host'] = getKey(config, 'BUILDLOGS_REDIS.host') || getKey(config, 'USER_EVENTS_REDIS.host');
$scope.mapped['redis']['port'] = getKey(config, 'BUILDLOGS_REDIS.port') || getKey(config, 'USER_EVENTS_REDIS.port');
@@ -258,6 +287,7 @@ angular.module("core-config-setup", ['angularFileUpload'])
// Add mapped logic.
$scope.$watch('mapped.GITHUB_LOGIN_KIND', githubSelector('GITHUB_LOGIN_CONFIG'));
$scope.$watch('mapped.GITHUB_TRIGGER_KIND', githubSelector('GITHUB_TRIGGER_CONFIG'));
+ $scope.$watch('mapped.GITLAB_TRIGGER_KIND', gitlabSelector('GITLAB_TRIGGER_KIND'));
$scope.$watch('mapped.redis.host', redisSetter('host'));
$scope.$watch('mapped.redis.port', redisSetter('port'));
diff --git a/util/config/validator.py b/util/config/validator.py
index aff6faadb..ab1ee696d 100644
--- a/util/config/validator.py
+++ b/util/config/validator.py
@@ -12,9 +12,10 @@ from flask import Flask
from flask.ext.mail import Mail, Message
from data.database import validate_database_url, User
from storage import get_storage_driver
-from app import app, CONFIG_PROVIDER
+from app import app, CONFIG_PROVIDER, get_app_url
from auth.auth_context import get_authenticated_user
-from util.oauth import GoogleOAuthConfig, GithubOAuthConfig
+from util.oauth import GoogleOAuthConfig, GithubOAuthConfig, GitLabOAuthConfig
+from bitbucket import BitBucket
logger = logging.getLogger(__name__)
@@ -99,6 +100,32 @@ def _validate_mailing(config):
test_mail.send(test_msg)
+def _validate_gitlab(config):
+ """ Validates the OAuth credentials and API endpoint for a GitLab service. """
+ github_config = config.get('GITLAB_TRIGGER_CONFIG')
+ if not github_config:
+ raise Exception('Missing GitLab client id and client secret')
+
+ endpoint = github_config.get('GITLAB_ENDPOINT')
+ if not endpoint:
+ raise Exception('Missing GitLab Endpoint')
+
+ if endpoint.find('http://') != 0 and endpoint.find('https://') != 0:
+ raise Exception('GitLab Endpoint must start with http:// or https://')
+
+ if not github_config.get('CLIENT_ID'):
+ raise Exception('Missing Client ID')
+
+ if not github_config.get('CLIENT_SECRET'):
+ raise Exception('Missing Client Secret')
+
+ client = app.config['HTTPCLIENT']
+ oauth = GitLabOAuthConfig(config, 'GITLAB_TRIGGER_CONFIG')
+ result = oauth.validate_client_id_and_secret(client, app.config)
+ if not result:
+ raise Exception('Invalid client id or client secret')
+
+
def _validate_github(config_key):
return lambda config: _validate_github_with_key(config_key, config)
@@ -107,11 +134,11 @@ def _validate_github_with_key(config_key, config):
""" Validates the OAuth credentials and API endpoint for a Github service. """
github_config = config.get(config_key)
if not github_config:
- raise Exception('Missing Github client id and client secret')
+ raise Exception('Missing GitHub client id and client secret')
endpoint = github_config.get('GITHUB_ENDPOINT')
if not endpoint:
- raise Exception('Missing Github Endpoint')
+ raise Exception('Missing GitHub Endpoint')
if endpoint.find('http://') != 0 and endpoint.find('https://') != 0:
raise Exception('Github Endpoint must start with http:// or https://')
@@ -127,7 +154,7 @@ def _validate_github_with_key(config_key, config):
client = app.config['HTTPCLIENT']
oauth = GithubOAuthConfig(config, config_key)
- result = oauth.validate_client_id_and_secret(client)
+ result = oauth.validate_client_id_and_secret(client, app.config)
if not result:
raise Exception('Invalid client id or client secret')
@@ -137,6 +164,28 @@ def _validate_github_with_key(config_key, config):
raise Exception('Invalid organization: %s' % org_id)
+def _validate_bitbucket(config):
+ """ Validates the config for BitBucket. """
+ trigger_config = config.get('BITBUCKET_TRIGGER_CONFIG')
+ if not trigger_config:
+ raise Exception('Missing client ID and client secret')
+
+ if not trigger_config.get('CONSUMER_KEY'):
+ raise Exception('Missing Consumer Key')
+
+ if not trigger_config.get('CONSUMER_SECRET'):
+ raise Exception('Missing Consumer Secret')
+
+ key = trigger_config['CONSUMER_KEY']
+ secret = trigger_config['CONSUMER_SECRET']
+ callback_url = '%s/oauth1/bitbucket/callback/trigger/' % (get_app_url())
+
+ bitbucket_client = BitBucket(key, secret, callback_url)
+ (result, _, _) = bitbucket_client.get_authorization_url()
+ if not result:
+ raise Exception('Invaid consumer key or secret')
+
+
def _validate_google_login(config):
""" Validates the Google Login client ID and secret. """
google_login_config = config.get('GOOGLE_LOGIN_CONFIG')
@@ -151,7 +200,7 @@ def _validate_google_login(config):
client = app.config['HTTPCLIENT']
oauth = GoogleOAuthConfig(config, 'GOOGLE_LOGIN_CONFIG')
- result = oauth.validate_client_id_and_secret(client)
+ result = oauth.validate_client_id_and_secret(client, app.config)
if not result:
raise Exception('Invalid client id or client secret')
@@ -261,6 +310,8 @@ _VALIDATORS = {
'mail': _validate_mailing,
'github-login': _validate_github('GITHUB_LOGIN_CONFIG'),
'github-trigger': _validate_github('GITHUB_TRIGGER_CONFIG'),
+ 'gitlab-trigger': _validate_gitlab,
+ 'bitbucket-trigger': _validate_bitbucket,
'google-login': _validate_google_login,
'ssl': _validate_ssl,
'ldap': _validate_ldap,
diff --git a/util/oauth.py b/util/oauth.py
index dfae97a2f..33c9330d1 100644
--- a/util/oauth.py
+++ b/util/oauth.py
@@ -15,7 +15,7 @@ class OAuthConfig(object):
def user_endpoint(self):
raise NotImplementedError
- def validate_client_id_and_secret(self, http_client):
+ def validate_client_id_and_secret(self, http_client, app_config):
raise NotImplementedError
def client_id(self):
@@ -30,6 +30,13 @@ class OAuthConfig(object):
return endpoint
+ def get_redirect_uri(self, app_config, redirect_suffix=''):
+ return '%s://%s/oauth2/%s/callback%s' % (app_config['PREFERRED_URL_SCHEME'],
+ app_config['SERVER_HOSTNAME'],
+ self.service_name().lower(),
+ redirect_suffix)
+
+
def exchange_code_for_token(self, app_config, http_client, code, form_encode=False,
redirect_suffix=''):
payload = {
@@ -37,10 +44,7 @@ class OAuthConfig(object):
'client_secret': self.client_secret(),
'code': code,
'grant_type': 'authorization_code',
- 'redirect_uri': '%s://%s/oauth2/%s/callback%s' % (app_config['PREFERRED_URL_SCHEME'],
- app_config['SERVER_HOSTNAME'],
- self.service_name().lower(),
- redirect_suffix)
+ 'redirect_uri': self.get_redirect_uri(app_config, redirect_suffix)
}
headers = {
@@ -114,7 +118,7 @@ class GithubOAuthConfig(OAuthConfig):
api_endpoint = self._api_endpoint()
return self._get_url(api_endpoint, 'user/orgs')
- def validate_client_id_and_secret(self, http_client):
+ def validate_client_id_and_secret(self, http_client, app_config):
# First: Verify that the github endpoint is actually Github by checking for the
# X-GitHub-Request-Id here.
api_endpoint = self._api_endpoint()
@@ -176,7 +180,7 @@ class GoogleOAuthConfig(OAuthConfig):
def user_endpoint(self):
return 'https://www.googleapis.com/oauth2/v1/userinfo'
- def validate_client_id_and_secret(self, http_client):
+ def validate_client_id_and_secret(self, http_client, app_config):
# To verify the Google client ID and secret, we hit the
# https://www.googleapis.com/oauth2/v3/token endpoint with an invalid request. If the client
# ID or secret are invalid, we get returned a 403 Unauthorized. Otherwise, we get returned
@@ -219,8 +223,24 @@ class GitLabOAuthConfig(OAuthConfig):
def token_endpoint(self):
return self._get_url(self._endpoint(), '/oauth/token')
- def validate_client_id_and_secret(self, http_client):
- pass
+ def validate_client_id_and_secret(self, http_client, app_config):
+ url = self.token_endpoint()
+ redirect_uri = self.get_redirect_uri(app_config, redirect_suffix='trigger')
+ data = {
+ 'code': 'fakecode',
+ 'client_id': self.client_id(),
+ 'client_secret': self.client_secret(),
+ 'grant_type': 'authorization_code',
+ 'redirect_uri': redirect_uri
+ }
+
+ # We validate by checking the error code we receive from this call.
+ result = http_client.post(url, data=data, timeout=5)
+ value = result.json()
+ if not value:
+ return False
+
+ return value.get('error', '') != 'invalid_client'
def get_public_config(self):
return {