def create(self, request, username, service_id, password=None, hosting_url=None, local_site_name=None, *args, **kwargs): """Creates a hosting service account. The ``service_id`` is a registered HostingService ID. This must be known beforehand, and can be looked up in the Review Board administration UI. """ local_site = self._get_local_site(local_site_name) if not HostingServiceAccount.objects.can_create( request.user, local_site): return self._no_access_error(request.user) # Validate the service. service = get_hosting_service(service_id) if not service: return INVALID_FORM_DATA, { 'fields': { 'service': ['This is not a valid service name'], } } if service.self_hosted and not hosting_url: return INVALID_FORM_DATA, { 'fields': { 'hosting_url': ['This field is required'], } } account = HostingServiceAccount(service_name=service_id, username=username, hosting_url=hosting_url, local_site=local_site) service = account.service if service.needs_authorization: try: service.authorize(request, username, password, hosting_url, local_site_name) except AuthorizationError as e: return HOSTINGSVC_AUTH_ERROR, { 'reason': six.text_type(e), } account.save() return 201, { self.item_result_key: account, }
def create(self, request, username, service_id, password=None, hosting_url=None, local_site_name=None, *args, **kwargs): """Creates a hosting service account. The ``service_id`` is a registered HostingService ID. This must be known beforehand, and can be looked up in the Review Board administration UI. """ local_site = self._get_local_site(local_site_name) if not HostingServiceAccount.objects.can_create(request.user, local_site): return self._no_access_error(request.user) # Validate the service. service = get_hosting_service(service_id) if not service: return INVALID_FORM_DATA, { 'fields': { 'service': ['This is not a valid service name'], } } if service.self_hosted and not hosting_url: return INVALID_FORM_DATA, { 'fields': { 'hosting_url': ['This field is required'], } } account = HostingServiceAccount(service_name=service_id, username=username, hosting_url=hosting_url, local_site=local_site) service = account.service if service.needs_authorization: try: service.authorize(request, username, password, hosting_url, local_site_name) except AuthorizationError as e: return HOSTINGSVC_AUTH_ERROR, { 'reason': six.text_type(e), } account.save() return 201, { self.item_result_key: account, }
def create_hosting_account(self, use_url=None, local_site=None, data=None): """Create a hosting account to test with. Args: use_url (unicode, optional): Whether the account should be attached to a given hosting URL, for self-hosted services. If set, this will use ``https://example.com``. local_site (reviewboard.site.models.LocalSite, optional): A Local Site to attach the account to. data (dict, optional): Optional data to set for the account. If this is ``None``, :py:attr:`default_account_data` will be used. Returns: reviewboard.hostingsvcs.models.HostingServiceAccount: The new hosting service account. """ if use_url is None: use_url = self.default_use_hosting_url if use_url: hosting_url = self.default_hosting_url else: hosting_url = None account = HostingServiceAccount(service_name=self.service_name, username=self.default_username, hosting_url=hosting_url, local_site=local_site) if data is not None: account.data = data else: account.data = self.default_account_data account.save() return account
def _clean_hosting_info(self): """Clean the hosting service information. If using a hosting service, this will validate that the data provided is valid on that hosting service. Then it will create an account and link it, if necessary, with the hosting service. """ hosting_type = self.cleaned_data['hosting_type'] if hosting_type == self.NO_HOSTING_SERVICE_ID: self.data['hosting_account'] = None self.cleaned_data['hosting_account'] = None return # This should have been caught during validation, so we can assume # it's fine. hosting_service_cls = get_hosting_service(hosting_type) assert hosting_service_cls # Validate that the provided tool is valid for the hosting service. tool_name = self.cleaned_data['tool'].name if tool_name not in hosting_service_cls.supported_scmtools: self.errors['tool'] = self.error_class([ _('This tool is not supported on the given hosting service') ]) return # Now make sure all the account info is correct. hosting_account = self.cleaned_data['hosting_account'] username = self.cleaned_data['hosting_account_username'] password = self.cleaned_data['hosting_account_password'] if hosting_service_cls.self_hosted: hosting_url = self.cleaned_data['hosting_url'] or None else: hosting_url = None if hosting_account and hosting_account.hosting_url != hosting_url: self.errors['hosting_account'] = self.error_class([ _('This account is not compatible with this hosting service ' 'configuration'), ]) return elif hosting_account and not username: username = hosting_account.username elif not hosting_account and not username: self.errors['hosting_account'] = self.error_class([ _('An account must be linked in order to use this hosting ' 'service'), ]) return if not hosting_account: # See if this account with the supplied credentials already # exists. If it does, we don't want to create a new entry. try: hosting_account = HostingServiceAccount.objects.get( service_name=hosting_type, username=username, hosting_url=hosting_url, local_site=self.local_site) except HostingServiceAccount.DoesNotExist: # That's fine. We're just going to create it later. pass plan = self.cleaned_data['repository_plan'] or self.DEFAULT_PLAN_ID # Set the main repository fields (Path, Mirror Path, etc.) based on # the field definitions in the hosting service. # # This will take into account the hosting service's form data for # the given repository plan, the main form data, and the hosting # account information. # # It's expected that the required fields will have validated by now. repository_form = self.repository_forms[hosting_type][plan] field_vars = repository_form.cleaned_data.copy() field_vars.update(self.cleaned_data) # If the hosting account needs to authorize and link with an external # service, attempt to do so and watch for any errors. # # If it doesn't need to link with it, we'll just create an entry # with the username and save it. if not hosting_account: hosting_account = HostingServiceAccount( service_name=hosting_type, username=username, hosting_url=hosting_url, local_site=self.local_site) if (hosting_service_cls.needs_authorization and not hosting_account.is_authorized): try: hosting_account.service.authorize( username, password, hosting_url, local_site_name=self.local_site_name) except AuthorizationError as e: self.errors['hosting_account'] = self.error_class([ _('Unable to link the account: %s') % e, ]) return except Exception as e: self.errors['hosting_account'] = self.error_class([ _('Unknown error when linking the account: %s') % e, ]) return # Flag that we've linked the account. If there are any # validation errors, and this flag is set, we tell the user # that we successfully linked and they don't have to do it # again. self.hosting_account_linked = True hosting_account.save() self.data['hosting_account'] = hosting_account self.cleaned_data['hosting_account'] = hosting_account try: self.cleaned_data.update(hosting_service_cls.get_repository_fields( hosting_account.username, hosting_account.hosting_url, plan, tool_name, field_vars)) except KeyError as e: raise ValidationError([unicode(e)])
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 save(self, allow_authorize=True, 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. 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): # 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 _clean_hosting_info(self): """Clean the hosting service information. If using a hosting service, this will validate that the data provided is valid on that hosting service. Then it will create an account and link it, if necessary, with the hosting service. """ hosting_type = self.cleaned_data['hosting_type'] if hosting_type == self.NO_HOSTING_SERVICE_ID: self.data['hosting_account'] = None self.cleaned_data['hosting_account'] = None return # This should have been caught during validation, so we can assume # it's fine. hosting_service_cls = get_hosting_service(hosting_type) assert hosting_service_cls # Validate that the provided tool is valid for the hosting service. tool_name = self.cleaned_data['tool'].name if tool_name not in hosting_service_cls.supported_scmtools: self.errors['tool'] = self.error_class([ _('This tool is not supported on the given hosting service') ]) return # Now make sure all the account info is correct. hosting_account = self.cleaned_data['hosting_account'] username = self.cleaned_data['hosting_account_username'] password = self.cleaned_data['hosting_account_password'] if hosting_service_cls.self_hosted: hosting_url = self.cleaned_data['hosting_url'] or None else: hosting_url = None if hosting_service_cls.supports_two_factor_auth: two_factor_auth_code = \ self.cleaned_data['hosting_account_two_factor_auth_code'] else: two_factor_auth_code = None if hosting_account and hosting_account.hosting_url != hosting_url: self.errors['hosting_account'] = self.error_class([ _('This account is not compatible with this hosting service ' 'configuration'), ]) return elif hosting_account and not username: username = hosting_account.username elif not hosting_account and not username: self.errors['hosting_account'] = self.error_class([ _('An account must be linked in order to use this hosting ' 'service'), ]) return if not hosting_account: # See if this account with the supplied credentials already # exists. If it does, we don't want to create a new entry. try: hosting_account = HostingServiceAccount.objects.get( service_name=hosting_type, username=username, hosting_url=hosting_url, local_site=self.local_site) except HostingServiceAccount.DoesNotExist: # That's fine. We're just going to create it later. pass plan = self.cleaned_data['repository_plan'] or self.DEFAULT_PLAN_ID # Set the main repository fields (Path, Mirror Path, etc.) based on # the field definitions in the hosting service. # # This will take into account the hosting service's form data for # the given repository plan, the main form data, and the hosting # account information. # # It's expected that the required fields will have validated by now. repository_form = self.repository_forms[hosting_type][plan] field_vars = repository_form.cleaned_data.copy() field_vars.update(self.cleaned_data) # If the hosting account needs to authorize and link with an external # service, attempt to do so and watch for any errors. # # If it doesn't need to link with it, we'll just create an entry # with the username and save it. if not hosting_account: hosting_account = HostingServiceAccount( service_name=hosting_type, username=username, hosting_url=hosting_url, local_site=self.local_site) if (hosting_service_cls.needs_authorization and not hosting_account.is_authorized): # Attempt to authorize the account. hosting_service = None plan = None if hosting_service_cls: hosting_service = hosting_service_cls(hosting_account) if hosting_service: plan = (self.cleaned_data['repository_plan'] or self.DEFAULT_PLAN_ID) repository_extra_data = self._build_repository_extra_data( hosting_service, hosting_type, plan) try: hosting_account.service.authorize( username, password, hosting_url=hosting_url, two_factor_auth_code=two_factor_auth_code, tool_name=tool_name, local_site_name=self.local_site_name, **repository_extra_data) except TwoFactorAuthCodeRequiredError as e: self.errors['hosting_account'] = \ self.error_class([six.text_type(e)]) hosting_info = self.hosting_service_info[hosting_type] hosting_info['needs_two_factor_auth_code'] = True return except AuthorizationError as e: self.errors['hosting_account'] = self.error_class([ _('Unable to link the account: %s') % e, ]) return except Exception as e: self.errors['hosting_account'] = self.error_class([ _('Unknown error when linking the account: %s') % e, ]) return # Flag that we've linked the account. If there are any # validation errors, and this flag is set, we tell the user # that we successfully linked and they don't have to do it # again. self.hosting_account_linked = True hosting_account.save() self.data['hosting_account'] = hosting_account self.cleaned_data['hosting_account'] = hosting_account try: self.cleaned_data.update(hosting_service_cls.get_repository_fields( hosting_account.username, hosting_account.hosting_url, plan, tool_name, field_vars)) except KeyError as e: raise ValidationError([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 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