Example #1
0
    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))
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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'))
Example #5
0
 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.'))
Example #6
0
    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()
Example #7
0
    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'))
Example #8
0
    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()
Example #9
0
    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()
Example #10
0
    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)
Example #11
0
    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,
        })
Example #13
0
    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()
Example #14
0
    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)
Example #15
0
    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)
Example #17
0
    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()
Example #18
0
    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)
Example #19
0
    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
Example #20
0
    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
Example #21
0
    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
Example #22
0
    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))
Example #23
0
    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
Example #24
0
    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)
Example #25
0
    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,
        )
Example #26
0
    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
Example #27
0
    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,
        )
Example #28
0
    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
Example #29
0
    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