def authorize(self, username, password, hosting_url, local_site_name=None, *args, **kwargs): site = Site.objects.get_current() siteconfig = SiteConfiguration.objects.get_current() site_url = '%s://%s%s' % ( siteconfig.get('site_domain_method'), site.domain, local_site_reverse('root', local_site_name=local_site_name)) try: body = { 'scopes': [ 'user', 'repo', ], 'note': 'Access for Review Board', 'note_url': site_url, } # If the site is using a registered GitHub application, # send it in the requests. This will gain the benefits of # a GitHub application, such as higher rate limits. if (hasattr(settings, 'GITHUB_CLIENT_ID') and hasattr(settings, 'GITHUB_CLIENT_SECRET')): body.update({ 'client_id': settings.GITHUB_CLIENT_ID, 'client_secret': settings.GITHUB_CLIENT_SECRET, }) rsp, headers = self._json_post(url=self.get_api_url(hosting_url) + 'authorizations', username=username, password=password, body=simplejson.dumps(body)) except (urllib2.HTTPError, urllib2.URLError), e: data = e.read() try: rsp = simplejson.loads(data) except: rsp = None if rsp and 'message' in rsp: raise AuthorizationError(rsp['message']) else: raise AuthorizationError(str(e))
def _api_get(self, url): """Make a GET request to the Review Board Gateway API. Delegate to the client's http_get function but first add a PRIVATE-TOKEN in the header for authentication. """ try: data, headers = self.client.http_get(url, headers={ 'PRIVATE-TOKEN': self._get_private_token(), }) return data, headers except HTTPError as e: if e.code == 401: raise AuthorizationError( ugettext('The login or password is incorrect.')) elif e.code == 404: raise else: logging.warning('Failed to execute a GET request at %s: %s', url, e, exc_info=1) raise
def _api_head(self, url): """Make a HEAD request to the Review Board Gateway API. Delegate to the client's http_request function using the method HEAD but first add a PRIVATE-TOKEN in the header for authentication. """ try: data, headers = self.client.http_request( url, headers={ 'PRIVATE-TOKEN': self._get_private_token(), }, method='HEAD') return headers except HTTPError as e: if e.code == 401: raise AuthorizationError( ugettext('The username or password is incorrect.')) elif e.code == 404: raise else: logger.exception('Failed to execute a HEAD request at %s: %s', url, e) raise
def _check_api_error(self, e): data = e.read() try: rsp = json.loads(data) except: rsp = None message = data if rsp and 'error' in rsp: error = rsp['error'] if 'message' in error: message = error['message'] if message: message = six.text_type(message) if e.code == 401: raise AuthorizationError( message or ugettext('Invalid Bitbucket username or password')) else: raise HostingServiceError( message or ugettext('Unknown error when talking to Bitbucket'))
def _raise_auth_error(self, message=None): raise AuthorizationError( message or ugettext('Invalid Bitbucket username or password. Make sure ' 'you are using your Bitbucket username and not e-mail ' 'address, and are using an app password if two-factor ' 'authentication is enabled.'))
def authorize(self, username, password, hosting_url, *args, **kwargs): """Authorizes the GitLab repository. GitLab uses HTTP Basic Auth for the API, so this will store the provided password, encrypted, for use in later API requests. """ # This will raise an exception if it fails, which the form will # catch. try: rsp, headers = self._json_post(url=self._build_api_url( hosting_url, 'session'), fields={ 'login': username, 'password': password, }) except HTTPError as e: if e.code == 404: raise HostingServiceError( ugettext('A GitLab server was not found at the ' 'provided URL.')) elif e.code == 401: raise AuthorizationError( ugettext('The username or password is incorrect.')) else: raise self.account.data['private_token'] = \ encrypt_password(rsp['private_token']) self.account.save()
def _check_api_error(self, e): data = e.read() try: rsp = json.loads(data) except: rsp = None message = data if rsp and 'error' in rsp: error = rsp['error'] if 'message' in error: message = error['message'] if message: message = six.text_type(message) if e.code == 401: raise AuthorizationError( message or ugettext('Invalid Bitbucket username or password')) elif e.code == 404: # We don't have a path here, but it will be filled in inside # _api_get_src. raise FileNotFoundError('') else: raise HostingServiceError( message or ugettext('Unknown error when talking to Bitbucket'))
def authorize(self, username, password, hosting_url, *args, **kwargs): """Authorize the Review Board Gateway repository. Review Board Gateway uses HTTP Basic Auth, so this will store the provided password, encrypted, for use in later API requests. Similar to GitLab's API, Review Board Gateway will return a private token on session authentication. """ try: response = self.client.http_post(url='%s/session' % hosting_url, username=username, password=password) except HTTPError as e: if e.code == 401: raise AuthorizationError( ugettext('The username or password is incorrect.')) elif e.code == 404: raise HostingServiceError( ugettext('A Review Board Gateway server was not found at ' 'the provided URL.')) else: logger.exception('Failed authorization at %s/session: %s', hosting_url, e) raise self.account.data['private_token'] = \ encrypt_password(response.json['private_token']) self.account.save()
def authorize(self, username, password, hosting_url=None, local_site_name=None, *args, **kwargs): """Authorize an account for the hosting service. Args: username (unicode): The username for the account. password (unicode): The Personal Access Token for the account. hosting_url (unicode): The hosting URL for the service, if self-hosted. local_site_name (unicode, optional): The Local Site name, if any, that the account should be bound to. *args (tuple): Extra unused positional arguments. **kwargs (dict): Extra keyword arguments containing values from the repository's configuration. Raises: reviewboard.hostingsvcs.errors.AuthorizationError: The credentials provided were not valid. """ # Try to reach an API resource with the provided credentials. rsp = self.client.http_get('%suser' % self.get_api_url(hosting_url), username=username, password=password) # Check to make sure this token has all the necessary scopes. token_scopes = set(rsp.get_header('x-oauth-scopes', '').split(', ')) required_scopes = set(self.REQUIRED_SCOPES) missing_scopes = required_scopes - token_scopes if missing_scopes: raise AuthorizationError( _('This GitHub Personal Access Token must have the ' 'following scopes enabled: %(scopes)s') % { 'scopes': ', '.join(sorted(missing_scopes)), }) if 'authorization' in self.account.data: # This is an older GitHub linked account, which used the legacy # authorizations API to generate the token. This stopped being # supported in Review Board 3.0.18. del self.account.data['authorization'] self.account.data['personal_token'] = encrypt_password(password) self.account.save()
def api_get(self, url, raw_content=False): """Perform an HTTP GET request to the API. Args: url (unicode): The full URL to the API resource. raw_content (bool, optional): If set to ``True``, the raw content of the result will be returned, instead of a parsed XML result. Returns: object: The parsed content of the result, as a dictionary, or the raw bytes content if ``raw_content`` is ``True``. """ hosting_service = self.hosting_service try: account_data = hosting_service.account.data api_username = '******' % (account_data['domain'], hosting_service.account.username) api_key = decrypt_password(account_data['api_key']) response = self.http_get( url, username=api_username, password=api_key, headers={ 'Accept': self.API_MIMETYPE, }) data = response.data if raw_content: return data else: return self.parse_xml(data) except HTTPError as e: data = e.read() msg = str(e) rsp = self.parse_xml(data) if rsp and 'errors' in rsp: errors = rsp['errors'] if 'error' in errors: msg = errors['error'] if e.code == 401: raise AuthorizationError(msg) else: raise HostingServiceAPIError(msg, http_code=e.code, rsp=rsp) except URLError as e: raise HostingServiceAPIError(e.reason)
def authorize(self, username, password, local_site_name=None, *args, **kwargs): site = Site.objects.get_current() siteconfig = SiteConfiguration.objects.get_current() site_url = '%s://%s%s' % ( siteconfig.get('site_domain_method'), site.domain, local_site_reverse('root', local_site_name=local_site_name)) try: rsp, headers = self._json_post(url=self.API_URL + 'authorizations', username=username, password=password, body=simplejson.dumps({ 'scopes': [ 'user', 'repo', ], 'note': 'Access for Review Board', 'note_url': site_url, })) except (urllib2.HTTPError, urllib2.URLError), e: data = e.read() try: rsp = simplejson.loads(data) except: rsp = None if rsp and 'message' in rsp: raise AuthorizationError(rsp['message']) else: raise AuthorizationError(str(e))
def authorize(self, username, password, hosting_url, local_site_name=None, two_factor_auth_code=None, *args, **kwargs): if username == 'baduser': raise AuthorizationError('The username is very very bad.') elif username == '2fa-user' and two_factor_auth_code != '123456': raise TwoFactorAuthCodeRequiredError('Enter your 2FA code.') self.account.data.update({ 'username': username, 'password': password, 'hosting_url': hosting_url, 'local_site_name': local_site_name, })
def authorize(self, username, password, credentials, *args, **kwargs): """Authorize an account for Codebase. Codebase usees HTTP Basic Auth with an API username (consisting of the Codebase team's domain and the account username) and an API key (for the password) for API calls, and a standard username/password for Subversion repository access. We need to store all of this. Args: username (unicode): The username to authorize. password (unicode): The API token used as a password. credentials (dict): Additional credentials from the authentication form. *args (tuple): Extra unused positional arguments. **kwargs (dict): Extra unused keyword arguments. Raises: reviewboard.hostingsvcs.errors.AuthorizationError: The credentials provided were not valid. """ self.account.data.update({ 'domain': credentials['domain'], 'api_key': encrypt_password(credentials['api_key']), 'password': encrypt_password(password), }) # Test the account to make sure the credentials are fine. Note that # we can only really sanity-check the API token, domain, and username # from here. There's no way good way to check the actual password, # which we only use for Subversion repositories. # # This will raise a suitable error message if authorization fails. try: self.client.api_get_public_keys(username) except AuthorizationError: raise AuthorizationError( ugettext('One or more of the credentials provided were not ' 'accepted by Codebase.')) self.account.save()
def _check_api_error(self, e): data = e.read() try: rsp = json.loads(data.decode('utf-8')) except: rsp = None if rsp and 'message' in rsp: if e.code == 401: raise AuthorizationError(rsp['message'], http_code=e.code) raise HostingServiceError(rsp['message'], http_code=e.code) else: raise HostingServiceError(six.text_type(e), http_code=e.code)
def _check_api_error(self, rsp, raw_content=False): if raw_content: try: rsp = json.loads(rsp) except: rsp = None if rsp and 'errors' in rsp: # Look for certain errors. for error_info in rsp['errors']: if error_info['codeError'] in ('BadAuthentication', 'InvalidToken'): raise AuthorizationError(error_info['sError']) raise KilnAPIError(rsp['errors'])
def process_http_error(self, request, e): """Process an HTTP error, raising a result. This will look at the error, raising a more suitable exception in its place. Args: request (reviewboard.hostingsvcs.service.HostingServiceHTTPRequest, unused): The request that resulted in an error. e (urllib2.URLError): The error to check. Raises: reviewboard.hostingsvcs.errors.AuthorizationError: The credentials provided were not valid. reviewboard.hostingsvcs.errors.HostingServiceAPIError: An error occurred communicating with the API. An unparsed payload is available. reviewboard.hostingsvcs.errors.HostingServiceError: There was an unexpected error performing the request. reviewboard.scmtools.errors.UnverifiedCertificateError: The SSL certificate was not able to be verified. """ # Perform any default checks. super(ReviewBoardGatewayClient, self).process_http_error(request, e) if isinstance(e, HTTPError): code = e.getcode() if e.code == 401: raise AuthorizationError( ugettext('The username or password is incorrect.')) elif e.code == 404: raise HostingServiceAPIError( ugettext('The API endpoint was not found.'), http_code=code) else: msg = e.read() raise HostingServiceAPIError(msg, http_code=code, rsp=msg) else: raise HostingServiceError(e.reason)
def authorize(self, username, password, hosting_url, *args, **kwargs): """Authorizes the GitLab repository. GitLab uses HTTP Basic Auth for the API, so this will store the provided password, encrypted, for use in later API requests. """ if self._is_email(username): login_key = 'email' else: login_key = 'login' # This will raise an exception if it fails, which the form will # catch. try: rsp, headers = self.client.json_post(url=self._build_api_url( hosting_url, 'session'), fields={ login_key: username, 'password': password, }) except HTTPError as e: if e.code == 404: raise HostingServiceError( ugettext('A GitLab server was not found at the ' 'provided URL.')) elif e.code == 401: raise AuthorizationError( ugettext('The username or password is incorrect.')) else: logging.exception( 'Unexpected HTTP error when linking GitLab ' 'account for %s: %s', username, e) raise HostingServiceError( ugettext('Unexpected HTTP error %s.') % e.code) except Exception as e: logging.exception( 'Unexpected error when linking GitLab account ' 'for %s: %s', username, e) raise HostingServiceError(ugettext('Unexpected error "%s"') % e) self.account.data['private_token'] = \ encrypt_password(rsp['private_token']) self.account.save()
def process_http_error(self, request, e): """Process an HTTP error, possibly raising a result. This will look at the error, possibly raising a more suitable exception in its place. It checks for SSL verification failures, bad credentials, and GitHub error payloads. Args: request (reviewboard.hostingsvcs.service. HostingServiceHTTPRequest): The request that resulted in an error. e (urllib2.URLError): The error to process. Raises: reviewboard.hostingsvcs.errors.AuthorizationError: The repository credentials are invalid. reviewboard.hostingsvcs.errors.HostingServiceError: There was an error with the request. Details are in the response. reviewboard.scmtools.errors.UnverifiedCertificateError: The SSL certificate was not able to be verified. """ super(GitHubClient, self).process_http_error(request, e) try: data = e.read() rsp = json.loads(data.decode('utf-8')) except Exception: rsp = None if rsp and 'message' in rsp: message = rsp['message'] if e.code == 401: raise AuthorizationError(message, http_code=e.code) raise HostingServiceError(message, http_code=e.code) else: raise HostingServiceError(six.text_type(e), http_code=e.code)
def _api_get(self, url, raw_content=False): """Makes a request to the GitLab API and returns the result.""" try: data, headers = self.client.http_get( url, headers={ 'Accept': 'application/json', 'PRIVATE-TOKEN': self._get_private_token(), }) if raw_content: return data, headers else: return json.loads(data), headers except HTTPError as e: if e.code == 401: raise AuthorizationError( ugettext('The login or password is incorrect.')) raise
def _api_get(self, url, raw_content=False, username=None, password=None): try: response = self.client.http_get(url, username=username or self.account.username, password=password or self.get_password(), headers={ 'Accept': 'application/json', }) if raw_content: return response.data else: return response.json except HTTPError as e: if e.code == 401: raise AuthorizationError( ugettext('The login or password is incorrect.')) raise
def _api_get(self, url, raw_content=False, username=None, password=None): try: data, headers = self._http_get(url, username=username or self.account.username, password=password or self.get_password(), headers={ 'Accept': 'application/json', }) if raw_content: return data else: return json.loads(data) except HTTPError as e: if e.code == 401: raise AuthorizationError( _('The login or password is incorrect.')) raise
def _check_api_error(self, e): data = e.read() try: rsp = json.loads(data) except: rsp = None if rsp and 'message' in rsp: response_info = e.info() x_github_otp = response_info.get('X-GitHub-OTP', '') if x_github_otp.startswith('required;'): raise TwoFactorAuthCodeRequiredError( _('Enter your two-factor authentication code. ' 'This code will be sent to you by GitHub.')) if e.code == 401: raise AuthorizationError(rsp['message']) raise HostingServiceError(rsp['message']) else: raise HostingServiceError(six.text_type(e))
def save(self, allow_authorize=True, force_authorize=False, extra_authorize_kwargs=None, trust_host=False, save=True): """Save the hosting account and authorize against the service. This will create or update a hosting account, based on the information provided in the form and to this method. :py:meth:`is_valid` must be called prior to saving. Args: allow_authorize (bool, optional): If ``True`` (the default), the account will be authorized against the hosting service. If ``False``, only the database entry for the account will be affected. force_authorize (bool, optional): Force the account to be re-authorized, if already authorized. extra_authorize_kwargs (dict, optional): Additional keyword arguments to provide for the :py:meth:`HostingService.authorize() <reviewboard.hostingsvcs.models.HostingService.authorize>` call. trust_host (bool, optional): Whether to trust the given host, even if the linked certificate is invalid or self-signed. save (bool, optional): Whether or not the created account should be saved. This is intended to be used by subclasses who want to add additional data to the resulting hosting account before saving. If this is ``False``, the caller must ensure the resulting hosting account is saved. Returns: reviewboard.hostingsvcs.models.HostingServiceAccount: The updated or created hosting service account. Raises: reviewboard.hostingsvcs.errors.AuthorizationError: Information needed to authorize was missing, or authorization failed. reviewboard.hostingsvcs.errors.TwoFactorAuthCodeRequiredError: A two-factor authentication code is required to authorize the account. A code will need to be provided to the form. """ if extra_authorize_kwargs is None: extra_authorize_kwargs = {} credentials = self.get_credentials() # Grab the username from the credentials, sanity-checking that it's # been provided as part of the get_credentials() result. try: username = credentials['username'] except KeyError: logging.exception( '%s.get_credentials() must return a "username" ' 'key.', self.__class__.__name__) raise AuthorizationError( ugettext('Hosting service implementation error: ' '%s.get_credentials() must return a "username" key.') % self.__class__.__name__) hosting_account = self.hosting_account hosting_service_id = self.hosting_service_cls.hosting_service_id hosting_url = self.cleaned_data.get('hosting_url') if not self.hosting_service_cls.self_hosted: assert hosting_url is None if hosting_account: # Update the username and hosting URL, if they've changed. hosting_account.username = username hosting_account.hosting_url = hosting_url else: # Fetch an existing hosting account based on the credentials and # parameters, if there is one. If not, we're going to create one, # but we won't save it until we've authorized. hosting_account_attrs = { 'service_name': hosting_service_id, 'username': username, 'hosting_url': hosting_url, 'local_site': self.local_site, } try: hosting_account = \ HostingServiceAccount.objects.get(**hosting_account_attrs) except HostingServiceAccount.DoesNotExist: # Create a new one, but don't save it yet. hosting_account = \ HostingServiceAccount(**hosting_account_attrs) if (allow_authorize and self.hosting_service_cls.needs_authorization and (not hosting_account.is_authorized or force_authorize)): # Attempt to authorize the account. if self.local_site: local_site_name = self.local_site.name else: local_site_name = None password = credentials.get('password') two_factor_auth_code = credentials.get('two_factor_auth_code') authorize_kwargs = dict( { 'username': username, 'password': password, 'hosting_url': hosting_url, 'two_factor_auth_code': two_factor_auth_code, 'local_site_name': local_site_name, 'credentials': credentials, }, **extra_authorize_kwargs) try: self.authorize(hosting_account, hosting_service_id, **authorize_kwargs) except UnverifiedCertificateError as e: if trust_host: hosting_account.accept_certificate(e.certificate) self.authorize(hosting_account, hosting_service_id, **authorize_kwargs) else: raise if save: hosting_account.save() return hosting_account
def authorize(self, username, password, hosting_url, two_factor_auth_code=None, local_site_name=None, *args, **kwargs): site = Site.objects.get_current() siteconfig = SiteConfiguration.objects.get_current() site_base_url = '%s%s' % (site.domain, local_site_reverse( 'root', local_site_name=local_site_name)) site_url = '%s://%s' % (siteconfig.get('site_domain_method'), site_base_url) note = 'Access for Review Board (%s - %s)' % (site_base_url, uuid.uuid4().hex[:7]) try: body = { 'scopes': [ 'user', 'repo', ], 'note': note, 'note_url': site_url, } # If the site is using a registered GitHub application, # send it in the requests. This will gain the benefits of # a GitHub application, such as higher rate limits. if (hasattr(settings, 'GITHUB_CLIENT_ID') and hasattr(settings, 'GITHUB_CLIENT_SECRET')): body.update({ 'client_id': settings.GITHUB_CLIENT_ID, 'client_secret': settings.GITHUB_CLIENT_SECRET, }) headers = {} if two_factor_auth_code: headers['X-GitHub-OTP'] = two_factor_auth_code rsp, headers = self.client.json_post( url=self.get_api_url(hosting_url) + 'authorizations', username=username, password=password, headers=headers, body=json.dumps(body)) except (HTTPError, URLError) as e: data = e.read() try: rsp = json.loads(data) except: rsp = None if rsp and 'message' in rsp: response_info = e.info() x_github_otp = response_info.get('X-GitHub-OTP', '') if x_github_otp.startswith('required;'): raise TwoFactorAuthCodeRequiredError( _('Enter your two-factor authentication code ' 'and re-enter your password to link your account. ' 'This code will be sent to you by GitHub.')) raise AuthorizationError(rsp['message']) else: raise AuthorizationError(six.text_type(e)) self._save_auth_data(rsp)
def _try_api_versions(self, hosting_url, path, http_method='get', use_json=False, **request_kwargs): """Try different API versions and return the first valid response. Args: hosting_url (unicode): The URL of the GitLab server. path (unicode): The API path to retrieve, not including :samp:`/api/v{version}`. http_method (unicode, optional): The method to use. Defaults to ``GET``. use_json (bool, optional): Whether or not to interpret the results as JSON. **request_kwargs (dict): Additional keyword arguments to pass to the request method. Returns: tuple: A 3-tuple of: * The API version (:py:class:`unicode`). * The response body (:py:class:`bytes` or :py:class:`dict`). * The response headers (:py:class:`dict`). Raises: reviewboard.scmtools.errors.AuthorizationError: There was an issue with the authorization credentials. GitLabAPIVersionError: The API version could be determined. """ http_method = http_method.lower() if use_json: method = getattr(self.client, 'json_%s' % http_method) else: method = getattr(self.client, 'http_%s' % http_method) errors = [] for api_version in ('4', '3'): url = self._build_api_url(hosting_url, path, api_version=api_version) try: rsp, headers = method(url, **request_kwargs) except HTTPError as e: if e.code == 401: raise AuthorizationError('The API token is invalid.') errors.append(e) except Exception as e: errors.append(e) else: return api_version, rsp, headers # Note that we're only going to list the error found in the first # HTTP GET attempt. It's more than likely that if we're unable to # look up any version URLs, the root cause will be the same. raise GitLabAPIVersionError( ugettext( 'Could not determine the GitLab API version for %(url)s ' 'due to an unexpected error (%(errors)s). Check to make sure ' 'the URL can be resolved from this server and that any SSL ' 'certificates are valid and trusted.') % { 'url': hosting_url, 'errors': errors[0], }, causes=errors, )
def _api_get(self, hosting_url=None, path=None, url=None, raw_content=False): """Make a request to the GitLab API and return the result. If ``hosting_url`` and ``path`` are provided, the API version will be deduced from the server. Otherwise, the full URL given in ``url`` will be used. Args: hosting_url (unicode, optional): The host of the repository. path (unicode, optional): The path after :samp:`/api/v{version}`. url (unicode, optional): If provided, the full URL to retrieve. Passing ``hosting_url`` and ``path`` should be preferred over this argument. raw_content (bool, optional): Whether or not to return the raw content (if ``True``) or to parse it as JSON (if ``False``). Defaults to ``False``. Returns: object: The response. Raises: reviewboard.scmtools.errors.AuthorizationError: There was an issue with the authorization credentials. urllib2.HTTPError: There was an error communicating with the server. """ if url: assert not hosting_url assert not path else: url = self._build_api_url(hosting_url, path) headers = { 'PRIVATE-TOKEN': self._get_private_token(), } if not raw_content: headers['Accept'] = 'application/json' try: response = self.client.http_get(url, headers) if raw_content: return response.data, response.headers else: return response.json, response.headers except HTTPError as e: if e.code == 401: raise AuthorizationError( ugettext('The login or password is incorrect.')) raise
def _try_api_versions(self, hosting_url, path, http_method='get', use_json=False, **request_kwargs): """Try different API versions and return the first valid response. Args: hosting_url (unicode): The URL of the GitLab server. path (unicode): The API path to retrieve, not including :samp:`/api/v{version}`. http_method (unicode, optional): The method to use. Defaults to ``GET``. use_json (bool, optional): Whether or not to interpret the results as JSON. **request_kwargs (dict): Additional keyword arguments to pass to the request method. Returns: tuple: A 3-tuple of: * The API version (:py:class:`unicode`). * The response body (:py:class:`bytes` or :py:class:`dict`). * The response headers (:py:class:`dict`). Raises: reviewboard.scmtools.errors.AuthorizationError: There was an issue with the authorization credentials. GitLabAPIVersionError: The API version could be determined. """ http_method = http_method.lower() if use_json: method = getattr(self.client, 'json_%s' % http_method) else: method = getattr(self.client, 'http_%s' % http_method) errors = [] for api_version in ('4', '3'): url = self._build_api_url(hosting_url, path, api_version=api_version) try: rsp, headers = method(url, **request_kwargs) except HTTPError as e: if e.code == 401: raise AuthorizationError('The API token is invalid.') errors.append(e) except Exception as e: errors.append(e) else: return api_version, rsp, headers raise GitLabAPIVersionError( 'Could not determine GitLab API version for "%s"' % hosting_url, errors, )
def save(self, allow_authorize=True, force_authorize=False, extra_authorize_kwargs={}): """Save the hosting account and authorize against the service. This will create or update a hosting account, based on the information provided in the form and to this method. :py:meth:`is_valid` must be called prior to saving. Args: allow_authorize (bool, optional): If ``True`` (the default), the account will be authorized against the hosting service. If ``False``, only the database entry for the account will be affected. force_authorize (bool, optional): Force the account to be re-authorized, if already authorized. extra_authorize_kwargs (dict, optional): Additional keyword arguments to provide for the :py:meth:`HostingService.authorize() <reviewboard.hostingsvcs.models.HostingService.authorize>` call. Returns: reviewboard.hostingsvcs.models.HostingServiceAccount: The updated or created hosting service account. Raises: reviewboard.hostingsvcs.errors.AuthorizationError: Information needed to authorize was missing, or authorziation failed. reviewboard.hostingsvcs.errors.TwoFactorAuthCodeRequiredError: A two-factor authentication code is required to authorize the account. A code will need to be provided to the form. """ credentials = self.get_credentials() # Grab the username from the credentials, sanity-checking that it's # been provided as part of the get_credentials() result. try: username = credentials['username'] except KeyError: logging.exception( '%s.get_credentials() must return a "username" ' 'key.', self.__class__.__name__) raise AuthorizationError( ugettext('Hosting service implementation error: ' '%s.get_credentials() must return a "username" key.') % self.__class__.__name__) hosting_account = self.hosting_account hosting_service_id = self.hosting_service_cls.hosting_service_id hosting_url = self.cleaned_data.get('hosting_url') if not self.hosting_service_cls.self_hosted: assert hosting_url is None if hosting_account: # Update the username and hosting URL, if they've changed. hosting_account.username = username hosting_account.hosting_url = hosting_url else: # Fetch an existing hosting account based on the credentials and # parameters, if there is one. If not, we're going to create one, # but we won't save it until we've authorized. hosting_account_attrs = { 'service_name': hosting_service_id, 'username': username, 'hosting_url': hosting_url, 'local_site': self.local_site, } try: hosting_account = \ HostingServiceAccount.objects.get(**hosting_account_attrs) except HostingServiceAccount.DoesNotExist: # Create a new one, but don't save it yet. hosting_account = \ HostingServiceAccount(**hosting_account_attrs) if (allow_authorize and self.hosting_service_cls.needs_authorization and (not hosting_account.is_authorized or force_authorize)): # Attempt to authorize the account. if self.local_site: local_site_name = self.local_site.name else: local_site_name = None password = credentials.get('password') two_factor_auth_code = credentials.get('two_factor_auth_code') try: hosting_account.service.authorize( username=username, password=password, hosting_url=hosting_url, two_factor_auth_code=two_factor_auth_code, local_site_name=local_site_name, credentials=credentials, **extra_authorize_kwargs) except TwoFactorAuthCodeRequiredError: # Mark this asrequired for the next form render. self.fields['hosting_account_two_factor_auth_code']\ .required = True # Re-raise the error. raise except AuthorizationError: logging.exception( 'Authorization error linking hosting ' 'account ID=%r for hosting service=%r, ' 'username=%r, LocalSite=%r', hosting_account.pk, hosting_service_id, username, local_site_name) # Re-raise the error. raise except Exception: logging.exception( 'Unknown error linking hosting account ' 'ID=%r for hosting service=%r, ' 'username=%r, LocalSite=%r', hosting_account.pk, hosting_service_id, username, local_site_name) # Re-raise the error. raise hosting_account.save() return hosting_account
def authorize(self, username, password, credentials, local_site_name=None, gerrit_url=None, *args, **kwargs): """Authorize against the Gerrit server. Args: username (unicode): The username to use for authentication. password unicode): The password to use for authentication. credentials (dict): The credentials from the authentication form. local_site_name (unicode, optional): The name of the :py:class:`~reviewboard.site.models.LocalSite` the repository is associated with. gerrit_url (unicode): The URL of the Gerrit server. *args (tuple): Ignored positional arguments. **kwargs (dict): Ignored keyword arguments. Raises: reviewboard.hostingsvcs.errors.AuthorizationError: The provided credentials were incorrect. """ if gerrit_url is None: raise AuthorizationError('Gerrit URL is required.') url = urljoin(gerrit_url, '/a/projects/') try: self.client.api_get(url, username=username, password=password, json=False) except HostingServiceError as e: if self.account.pk: self.account.data['authorized'] = False if e.http_code != 401: logger.error( 'Unknown HTTP response while authenticating with Gerrit ' 'at %s: %s', url, e) raise AuthorizationError( ugettext('Could not authenticate with Gerrit at %(url): ' '%(error)s') % { 'url': url, 'error': e.message, }, http_code=e.http_code) raise AuthorizationError( ugettext('Unable to authenticate to Gerrit at %(url)s. The ' 'username or password used may be invalid.') % { 'url': url, }, http_code=e.http_code) self.account.data['authorized'] = True