def create_bot(bot_name: str, signature: Signature) -> Optional[User]: cla.log.debug(f'create_bot - creating Bot: {bot_name}...') user_github_id = lookup_github_user(bot_name) if user_github_id != 0: project: Project = cla.utils.get_project_instance() try: project.load(signature.get_signature_project_id()) except DoesNotExist as err: cla.log.warning(f'create_bot - Unable to load project by id: {signature.get_signature_project_id()}' f' Unable to create bot: {bot_name}') return None the_company: Company = cla.utils.get_company_instance() try: the_company.load(signature.get_signature_reference_id()) except DoesNotExist as err: cla.log.warning(f'create_bot - Unable to load company by id: {signature.get_signature_reference_id()}' f' Unable to create bot: {bot_name}') return None user: User = cla.utils.get_user_instance() user.set_user_id(str(uuid.uuid4())) user.set_user_name(bot_name) user.set_user_github_username(bot_name) user.set_user_github_id(user_github_id) user.set_user_company_id(signature.get_signature_reference_id()) user.set_note(f'{datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")} Added as part of ' f'{project.get_project_name()}, approval list by ' f'{the_company.get_company_name()}') user.save() cla.log.debug(f'create_bot - created Bot: {user}') return user cla.log.warning(f'create_bot - unable to create bot: {bot_name} - unable to lookup name in GitHub.') return None
def add_cla_manager(auth_user, signature_id, lfid): """ Adds the LFID to the signature ACL and returns a new list of CLA Managers. :param username: username of the user :type username: string :param signature_id: The ID of the project :type signature_id: UUID :param lfid: the lfid (manager username) to be added to the project acl :type lfid: string """ # Find project signature = Signature() try: signature.load(str(signature_id)) except DoesNotExist as err: return {'errors': {'project_id': str(err)}} # Get Signature ACL signature_acl = signature.get_signature_acl() if auth_user.username not in signature_acl: return {'errors': {'user_id': 'You are not authorized to see the managers.'}} company.add_permission(auth_user, lfid, signature.get_signature_reference_id(), ignore_auth_user=True) # Get Company and Project instances try: project = get_project(signature.get_signature_project_id()) except DoesNotExist as err: return err try: company_instance = get_company(signature.get_signature_reference_id()) except DoesNotExist as err: return err # get cla managers for email content managers = get_cla_managers(auth_user.username, signature_id) # Add lfid to acl signature.add_signature_acl(lfid) signature.save() # send email to newly added CLA manager try: subject, body, recipients = add_cla_manager_email_content(lfid, project, company_instance, managers) get_email_service().send(subject, body, recipients) except Exception as err: return {'errors': {'Failed to send email for lfid: %s , %s ' % (lfid, err)}} event_data = f'{lfid} added as cla manager to Signature ACL for {signature.get_signature_id()}' Event.create_event( event_data=event_data, event_summary=event_data, event_type=EventType.AddCLAManager, contains_pii=True, ) return get_managers_dict(signature_acl)
def get_project_companies(project_id): """ Get a project's associated companies (via CCLA link). :param project_id: The ID of the project. :type project_id: string """ project = get_project_instance() try: project.load(str(project_id)) except DoesNotExist as err: return {'errors': {'project_id': str(err)}} # Get all reference_ids of signatures that match project_id AND are of reference type 'company'. # Return all the companies matching those reference_ids. signature = Signature() signatures = signature.get_signatures_by_project(str(project_id), signature_signed=True, signature_approved=True, signature_reference_type='company') company_ids = list(set([signature.get_signature_reference_id() for signature in signatures])) company = Company() all_companies = [comp.to_dict() for comp in company.all(company_ids)] all_companies = sorted(all_companies, key=lambda i: i['company_name'].casefold()) return all_companies
def handle_bots(bot_list: List[str], signature: Signature) -> None: cla.log.debug(f'Bots: {bot_list}') for bot_name in bot_list: try: user = cla.utils.get_user_instance() users = user.get_user_by_github_username(bot_name) if users is None: cla.log.debug(f'handle_bots - Bot: {bot_name} does not have a user record (None)') bot_user: User = create_bot(bot_name, signature) if bot_user is not None: create_bot_signature(bot_user, signature) else: # Bot does have a user account in the EasyCLA system found = False # Search the list of user records to see if we have a matching company for u in users: if u.get_user_company_id() == signature.get_signature_reference_id(): found = True cla.log.debug('handle_bots - found bot user account - ensuring the signature exists...') create_bot_signature(u, signature) break # We found matching users in our system, but didn't find one with a matching company if not found: cla.log.debug(f'handle_bots - unable to find user {bot_name} ' f'for company: {signature.get_signature_reference_id()} - ' 'creating user record that matches this company...') bot_user: User = create_bot(bot_name, signature) if bot_user is not None: create_bot_signature(bot_user, signature) else: cla.log.warning(f'handle_bots - failed to create user record for: {bot_name}') except DoesNotExist as err: cla.log.debug(f'handle_bots - bot: {bot_name} does not have a user record (DoesNotExist)')
def add_cla_manager(auth_user, signature_id, lfid): """ Adds the LFID to the signature ACL and returns a new list of CLA Managers. :param username: username of the user :type username: string :param signature_id: The ID of the project :type signature_id: UUID :param lfid: the lfid (manager username) to be added to the project acl :type lfid: string """ # Find project signature = Signature() try: signature.load(str(signature_id)) except DoesNotExist as err: return {'errors': {'project_id': str(err)}} # Get Signature ACL signature_acl = signature.get_signature_acl() if auth_user.username not in signature_acl: return {'errors': {'user_id': 'You are not authorized to see the managers.'}} company.add_permission(auth_user, lfid, signature.get_signature_reference_id(), ignore_auth_user=True) # Add lfid to acl signature.add_signature_acl(lfid) signature.save() return get_managers_dict(signature_acl)
def create_bot_signature(bot_user: User, signature: Signature) -> Optional[Signature]: cla.log.debug(f'create_bot_signature - locating Bot Signature for: {bot_user.get_user_name()}...') project: Project = cla.utils.get_project_instance() try: project.load(signature.get_signature_project_id()) except DoesNotExist as err: cla.log.warning(f'create_bot_signature - unable to load project by id: {signature.get_signature_project_id()}' f' Unable to create bot: {bot_user}') return None the_company: Company = cla.utils.get_company_instance() try: the_company.load(signature.get_signature_reference_id()) except DoesNotExist as err: cla.log.warning(f'create_bot_signature - unable to load company by id: {signature.get_signature_reference_id()}' f' Unable to create bot: {bot_user}') return None bot_sig: Signature = cla.utils.get_signature_instance() # First, before we create a new one, grab a list of employee signatures for this company/project existing_sigs: List[Signature] = bot_sig.get_employee_signatures_by_company_project_model( company_id=bot_user.get_user_company_id(), project_id=signature.get_signature_project_id()) # Check to see if we have an existing signature for this user/company/project combo for sig in existing_sigs: if sig.get_signature_reference_id() == bot_user.get_user_id(): cla.log.debug('create_bot_signature - found existing bot signature ' f'for user: {bot_user} ' f'with company: {the_company} ' f'for project: {project}') return sig # Didn't find an existing signature, let's create a new one cla.log.debug(f'create_bot_signature - creating Bot Signature: {bot_user.get_user_name()}...') bot_sig.set_signature_id(str(uuid.uuid4())) bot_sig.set_signature_project_id(signature.get_signature_project_id()) bot_sig.set_signature_reference_id(bot_user.get_user_id()) bot_sig.set_signature_document_major_version(signature.get_signature_document_major_version()) bot_sig.set_signature_document_minor_version(signature.get_signature_document_minor_version()) bot_sig.set_signature_approved(True) bot_sig.set_signature_signed(True) bot_sig.set_signature_type('cla') bot_sig.set_signature_reference_type('user') bot_sig.set_signature_user_ccla_company_id(bot_user.get_user_company_id()) bot_sig.set_note(f'{datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")} Added as part of ' f'{project.get_project_name()}, approval list by ' f'{the_company.get_company_name()}') bot_sig.save() cla.log.debug(f'create_bot_signature - created Bot Signature: {bot_sig}') return bot_sig
def update_signature( signature_id, # pylint: disable=too-many-arguments,too-many-return-statements,too-many-branches auth_user, signature_project_id=None, signature_reference_id=None, signature_reference_type=None, signature_type=None, signature_approved=None, signature_signed=None, signature_return_url=None, signature_sign_url=None, domain_whitelist=None, email_whitelist=None, github_whitelist=None, github_org_whitelist=None): """ Updates an signature and returns the newly updated signature in dict format. A value of None means the field should not be updated. :param signature_id: ID of the signature. :type signature_id: ID | None :param auth_user: the authenticated user :type auth_user: string :param signature_project_id: Project ID for this signature. :type signature_project_id: string | None :param signature_reference_id: Reference ID for this signature. :type signature_reference_id: string | None :param signature_reference_type: Reference type for this signature. :type signature_reference_type: ['user' | 'company'] | None :param signature_type: New signature type ('cla' or 'dco'). :type signature_type: string | None :param signature_signed: Whether this signature is signed or not. :type signature_signed: boolean | None :param signature_approved: Whether this signature is approved or not. :type signature_approved: boolean | None :param signature_return_url: The URL the user will be sent to after signing. :type signature_return_url: string | None :param signature_sign_url: The URL the user must visit to sign the signature. :type signature_sign_url: string | None :param domain_whitelist: the domain whitelist :param email_whitelist: the email whitelist :param github_whitelist: the github username whitelist :param github_org_whitelist: the github org whitelist :return: dict representation of the signature object. :rtype: dict """ fn = 'controllers.signature.update_signature' cla.log.debug(f'{fn} - loading signature by id: {str(signature_id)}') signature = Signature() try: # Try to load the signature to update. signature.load(str(signature_id)) old_signature = copy.deepcopy(signature) except DoesNotExist as err: return {'errors': {'signature_id': str(err)}} update_str = f'signature {signature_id} updates: \n ' if signature_project_id is not None: # make a note if the project id is set and doesn't match if signature.get_signature_project_id() != str(signature_project_id): cla.log.warning( f'{fn} - project IDs do not match => ' f'record project id: {signature.get_signature_project_id()} != ' f'parameter project id: {str(signature_project_id)}') try: signature.set_signature_project_id(str(signature_project_id)) update_str += f'signature_project_id updated to {signature_project_id} \n' except DoesNotExist as err: return {'errors': {'signature_project_id': str(err)}} # TODO: Ensure signature_reference_id exists. if signature_reference_id is not None: if signature.get_signature_reference_id() != str( signature_reference_id): cla.log.warning( f'{fn} - signature reference IDs do not match => ' f'record signature ref id: {signature.get_signature_reference_id()} != ' f'parameter signature ref id: {str(signature_reference_id)}') signature.set_signature_reference_id(signature_reference_id) if signature_reference_type is not None: signature.set_signature_reference_type(signature_reference_type) update_str += f'signature_reference_type updated to {signature_reference_type} \n' if signature_type is not None: if signature_type in ['cla', 'dco']: signature.set_signature_type(signature_type) update_str += f'signature_type updated to {signature_type} \n' else: return { 'errors': { 'signature_type': 'Invalid value passed. The accepted values are: (cla|dco)' } } if signature_signed is not None: try: val = hug.types.smart_boolean(signature_signed) signature.set_signature_signed(val) update_str += f'signature_signed updated to {signature_signed} \n' except KeyError: return { 'errors': { 'signature_signed': 'Invalid value passed in for true/false field' } } if signature_approved is not None: try: val = hug.types.smart_boolean(signature_approved) update_signature_approved(signature, val) update_str += f'signature_approved updated to {val} \n' except KeyError: return { 'errors': { 'signature_approved': 'Invalid value passed in for true/false field' } } if signature_return_url is not None: try: val = cla.hug_types.url(signature_return_url) signature.set_signature_return_url(val) update_str += f'signature_return_url updated to {val} \n' except KeyError: return { 'errors': { 'signature_return_url': 'Invalid value passed in for URL field' } } if signature_sign_url is not None: try: val = cla.hug_types.url(signature_sign_url) signature.set_signature_sign_url(val) update_str += f'signature_sign_url updated to {val} \n' except KeyError: return { 'errors': { 'signature_sign_url': 'Invalid value passed in for URL field' } } if domain_whitelist is not None: try: domain_whitelist = hug.types.multiple(domain_whitelist) signature.set_domain_whitelist(domain_whitelist) update_str += f'domain_whitelist updated to {domain_whitelist} \n' except KeyError: return { 'errors': { 'domain_whitelist': 'Invalid value passed in for the domain whitelist' } } if email_whitelist is not None: try: email_whitelist = hug.types.multiple(email_whitelist) signature.set_email_whitelist(email_whitelist) update_str += f'email_whitelist updated to {email_whitelist} \n' except KeyError: return { 'errors': { 'email_whitelist': 'Invalid value passed in for the email whitelist' } } if github_whitelist is not None: try: github_whitelist = hug.types.multiple(github_whitelist) signature.set_github_whitelist(github_whitelist) # A little bit of special logic to for GitHub whitelists that have bots bot_list = [ github_user for github_user in github_whitelist if is_github_bot(github_user) ] if bot_list is not None: handle_bots(bot_list, signature) update_str += f'github_whitelist updated to {github_whitelist} \n' except KeyError: return { 'errors': { 'github_whitelist': 'Invalid value passed in for the github whitelist' } } if github_org_whitelist is not None: try: github_org_whitelist = hug.types.multiple(github_org_whitelist) signature.set_github_org_whitelist(github_org_whitelist) update_str += f'github_org_whitelist updated to {github_org_whitelist} \n' except KeyError: return { 'errors': { 'github_org_whitelist': 'Invalid value passed in for the github org whitelist' } } event_data = update_str Event.create_event( event_data=event_data, event_summary=event_data, event_type=EventType.UpdateSignature, contains_pii=True, ) signature.save() notify_whitelist_change(auth_user=auth_user, old_signature=old_signature, new_signature=signature) return signature.to_dict()
def remove_cla_manager(username, signature_id, lfid): """ Removes the LFID from the project ACL :param username: username of the user :type username: string :param project_id: The ID of the project :type project_id: UUID :param lfid: the lfid (manager username) to be removed to the project acl :type lfid: string """ # Find project signature = Signature() try: signature.load(str(signature_id)) except DoesNotExist as err: return {'errors': {'signature_id': str(err)}} # Validate user is the manager of the project signature_acl = signature.get_signature_acl() if username not in signature_acl: return { 'errors': { 'user': "******" } } # Avoid to have an empty acl if len(signature_acl) == 1 and username == lfid: return { 'errors': { 'user': "******" } } # Remove LFID from the acl signature.remove_signature_acl(lfid) signature.save() # get cla managers for email content managers = get_cla_managers(username, signature_id) # Get Company and Project instances try: project = get_project(signature.get_signature_project_id()) except DoesNotExist as err: return err try: company_instance = get_company(signature.get_signature_reference_id()) except DoesNotExist as err: return err # Send email to removed CLA manager # send email to newly added CLA manager try: subject, body, recipients = remove_cla_manager_email_content( lfid, project, company_instance, managers) get_email_service().send(subject, body, recipients) except Exception as err: return { 'errors': {'Failed to send email for lfid: %s , %s ' % (lfid, err)} } event_data = f'User with lfid {lfid} removed from project ACL with signature {signature.get_signature_id()}' Event.create_event( event_data=event_data, event_summary=event_data, event_type=EventType.RemoveCLAManager, contains_pii=True, ) # Return modified managers return get_managers_dict(signature_acl)
def update_signature( signature_id, # pylint: disable=too-many-arguments,too-many-return-statements,too-many-branches signature_project_id=None, signature_reference_id=None, signature_reference_type=None, signature_type=None, signature_approved=None, signature_signed=None, signature_return_url=None, signature_sign_url=None, domain_whitelist=None, email_whitelist=None, github_whitelist=None, github_org_whitelist=None): """ Updates an signature and returns the newly updated signature in dict format. A value of None means the field should not be updated. :param signature_id: ID of the signature. :type signature_id: ID | None :param signature_project_id: Project ID for this signature. :type signature_project_id: string | None :param signature_reference_id: Reference ID for this signature. :type signature_reference_id: string | None :param signature_reference_type: Reference type for this signature. :type signature_reference_type: ['user' | 'company'] | None :param signature_type: New signature type ('cla' or 'dco'). :type signature_type: string | None :param signature_signed: Whether this signature is signed or not. :type signature_signed: boolean | None :param signature_approved: Whether this signature is approved or not. :type signature_approved: boolean | None :param signature_return_url: The URL the user will be sent to after signing. :type signature_return_url: string | None :param signature_sign_url: The URL the user must visit to sign the signature. :type signature_sign_url: string | None :param domain_whitelist: the domain whitelist :param email_whitelist: the email whitelist :param github_whitelist: the github username whitelist :param github_org_whitelist: the github org whitelist :return: dict representation of the signature object. :rtype: dict """ signature = Signature() try: # Try to load the signature to update. signature.load(str(signature_id)) except DoesNotExist as err: return {'errors': {'signature_id': str(err)}} if signature_project_id is not None: # make a note if the project id is set and doesn't match if signature.get_signature_project_id() != str(signature_project_id): cla.log.warning( 'update_signature() - project IDs do not match => ' f'record project id: {signature.get_signature_project_id()} != ' f'parameter project id: {str(signature_project_id)}') try: signature.set_signature_project_id(str(signature_project_id)) except DoesNotExist as err: return {'errors': {'signature_project_id': str(err)}} # TODO: Ensure signature_reference_id exists. if signature_reference_id is not None: if signature.get_signature_reference_id() != str( signature_reference_id): cla.log.warning( 'update_signature() - signature reference IDs do not match => ' f'record signature ref id: {signature.get_signature_reference_id()} != ' f'parameter signature ref id: {str(signature_reference_id)}') signature.set_signature_reference_id(signature_reference_id) if signature_reference_type is not None: signature.set_signature_reference_type(signature_reference_type) if signature_type is not None: if signature_type in ['cla', 'dco']: signature.set_signature_type(signature_type) else: return { 'errors': { 'signature_type': 'Invalid value passed. The accepted values are: (cla|dco)' } } if signature_signed is not None: try: val = hug.types.smart_boolean(signature_signed) signature.set_signature_signed(val) except KeyError as err: return { 'errors': { 'signature_signed': 'Invalid value passed in for true/false field' } } if signature_approved is not None: try: val = hug.types.smart_boolean(signature_approved) update_signature_approved(signature, val) except KeyError as err: return { 'errors': { 'signature_approved': 'Invalid value passed in for true/false field' } } if signature_return_url is not None: try: val = cla.hug_types.url(signature_return_url) signature.set_signature_return_url(val) except KeyError as err: return { 'errors': { 'signature_return_url': 'Invalid value passed in for URL field' } } if signature_sign_url is not None: try: val = cla.hug_types.url(signature_sign_url) signature.set_signature_sign_url(val) except KeyError as err: return { 'errors': { 'signature_sign_url': 'Invalid value passed in for URL field' } } if domain_whitelist is not None: try: domain_whitelist = hug.types.multiple(domain_whitelist) signature.set_domain_whitelist(domain_whitelist) except KeyError as err: return { 'errors': { 'domain_whitelist': 'Invalid value passed in for the domain whitelist' } } if email_whitelist is not None: try: email_whitelist = hug.types.multiple(email_whitelist) signature.set_email_whitelist(email_whitelist) except KeyError as err: return { 'errors': { 'email_whitelist': 'Invalid value passed in for the email whitelist' } } if github_whitelist is not None: try: github_whitelist = hug.types.multiple(github_whitelist) signature.set_github_whitelist(github_whitelist) # A little bit of special logic to for GitHub whitelists that have bots bot_list = [ github_user for github_user in github_whitelist if is_github_bot(github_user) ] if bot_list is not None: handle_bots(bot_list, signature) except KeyError as err: return { 'errors': { 'github_whitelist': 'Invalid value passed in for the github whitelist' } } if github_org_whitelist is not None: try: github_org_whitelist = hug.types.multiple(github_org_whitelist) signature.set_github_org_whitelist(github_org_whitelist) except KeyError as err: return { 'errors': { 'github_org_whitelist': 'Invalid value passed in for the github org whitelist' } } signature.save() return signature.to_dict()