def mutate(cls, _, info, **expenditure_input): with atomic_transaction(commit_at_end=True): reception_time = datetime.now() app.logger.info( f"Logging expenditure submitted by {current_user} of company {current_user.primary_company}" ) mission_id = expenditure_input.get("mission_id") mission = Mission.query.get(mission_id) user_id = expenditure_input.get("user_id") if user_id: user = User.query.get(user_id) if not user: raise AuthorizationError("Forbidden access") else: user = current_user expenditure = log_expenditure( submitter=current_user, user=user, mission=mission, type=expenditure_input["type"], reception_time=reception_time, ) return expenditure
def mutate(cls, _, info, employment_id, end_date=None): with atomic_transaction(commit_at_end=True): employment_end_date = end_date or date.today() employment = Employment.query.get(employment_id) if not employment or not company_admin_at( current_user, employment.company ): raise AuthorizationError( "Actor is not authorized to terminate the employment" ) if not employment.is_acknowledged or employment.end_date: raise InvalidResourceError( f"Employment is inactive or has already an end date" ) if employment.start_date > employment_end_date: raise InvalidParamsError( "End date is before the employment start date" ) app.logger.info( f"Terminating employment for User {employment.user_id} in Company {employment.company_id}" ) employment.end_date = employment_end_date db.session.add(employment) return employment
def mutate(cls, _, info, expenditure_id): with atomic_transaction(commit_at_end=True): reception_time = datetime.now() expenditure_to_dismiss = Expenditure.query.get(expenditure_id) if not expenditure_to_dismiss: raise AuthorizationError( "Actor is not authorized to dismiss the expenditure" ) mission = Mission.query.get(expenditure_to_dismiss.mission_id) check_actor_can_log_on_mission_for_user_at( current_user, expenditure_to_dismiss.user, mission, expenditure_to_dismiss.reception_time, ) if expenditure_to_dismiss.is_dismissed: raise ResourceAlreadyDismissedError( "Expenditure already dismissed" ) db.session.add(expenditure_to_dismiss) app.logger.info(f"Cancelling {expenditure_to_dismiss}") expenditure_to_dismiss.dismiss(reception_time) return Void(success=True)
def mutate(cls, _, info, **args): with atomic_transaction(commit_at_end=True): reception_time = datetime.now() mission = Mission.query.options(selectinload( Mission.activities)).get(args.get("mission_id")) user_id = args.get("user_id") if user_id: user = User.query.get(user_id) else: user = current_user if not mission: raise AuthorizationError( "Actor is not authorized to log on this mission at this time." ) if not user: raise AuthorizationError( f"Actor is not authorized to log for this user.") existing_mission_end = MissionEnd.query.filter( MissionEnd.user_id == user.id, MissionEnd.mission_id == mission.id, ).one_or_none() if existing_mission_end: raise MissionAlreadyEndedError( mission_end=existing_mission_end) app.logger.info(f"Ending mission {mission}") user_activities = mission.activities_for(user) last_activity = user_activities[-1] if user_activities else None end_time = args["end_time"] if last_activity: if last_activity.start_time > end_time or ( last_activity.end_time and last_activity.end_time > end_time): raise UnavailableSwitchModeError( "Invalid time for mission end because there are activities starting or ending after" ) if not last_activity.end_time: last_activity.revise( reception_time, end_time=args["end_time"], ) db.session.add( MissionEnd( submitter=current_user, reception_time=reception_time, user=user, mission=mission, )) return mission
def mutate(cls, _, info, registration_number, company_id, alias=None): with atomic_transaction(commit_at_end=True): vehicle = Vehicle( registration_number=registration_number, alias=alias, submitter=current_user, company_id=company_id, ) db.session.add(vehicle) app.logger.info(f"Created new vehicle {vehicle}") return vehicle
def review_employment(employment_id, reject): with atomic_transaction(commit_at_end=True): employment = Employment.query.get(employment_id) if not employment: raise AuthorizationError( "Actor is not authorized to review the employment" ) employment.validate_by(current_user, reject=reject) return employment
def mutate(cls, _, info, email): with atomic_transaction(commit_at_end=True): if current_user.email != email: current_user.email = email current_user.has_confirmed_email = True current_user.create_activation_link() try: mailer.send_activation_email(current_user, create_account=False) except Exception as e: app.logger.exception(e) return current_user
def mutate(cls, _, info, comment_id): with atomic_transaction(commit_at_end=True): reception_time = datetime.now() comment_to_dismiss = Comment.query.get(comment_id) if (not comment_to_dismiss or current_user.id != comment_to_dismiss.submitter_id): raise AuthorizationError( "Actor is not authorized to dismiss the comment.") db.session.add(comment_to_dismiss) comment_to_dismiss.dismiss(reception_time) return Void(success=True)
def mutate(cls, _, info, **activity_input): with atomic_transaction(commit_at_end=True): switch_mode = activity_input.get("switch", True) if switch_mode and activity_input.get("end_time"): raise InvalidParamsError( "Specifying an end time is not authorized in switch mode") reception_time = datetime.now() app.logger.info( f"Logging activity {activity_input['type']} submitted by {current_user}" ) mission_id = activity_input.get("mission_id") mission = Mission.query.options(selectinload( Mission.activities)).get(mission_id) user = current_user user_id = activity_input.get("user_id") if user_id: user = User.query.get(user_id) start_time = activity_input["start_time"] if user and switch_mode and mission: current_activity = mission.current_activity_for_at( user, start_time) if current_activity: check_actor_can_log_on_mission_for_user_at( current_user, user, mission, start_time) if current_activity.end_time: raise UnavailableSwitchModeError() if current_activity.type == activity_input.get("type"): return current_activity if not current_activity.end_time: current_activity.revise( reception_time, bypass_check=True, end_time=start_time, ) activity = log_activity( submitter=current_user, user=user, mission=mission, type=activity_input["type"], reception_time=reception_time, start_time=activity_input["start_time"], end_time=activity_input.get("end_time"), context=activity_input.get("context"), ) return activity
def mutate(cls, _, info, company_id, **kwargs): with atomic_transaction(commit_at_end=True): company = Company.query.get(company_id) is_there_something_updated = False for field, value in kwargs.items(): if value is not None: current_field_value = getattr(company, field) if current_field_value != value: is_there_something_updated = True setattr(company, field, value) if not is_there_something_updated: app.logger.warning("No setting was actually modified") db.session.add(company) return company
def mutate(cls, _, info, email, password=None): with atomic_transaction(commit_at_end=True): if not current_user.france_connect_id or current_user.password: raise AuthorizationError("Actor has already a login") current_user.email = email current_user.has_confirmed_email = True current_user.create_activation_link() if password: current_user.password = password try: mailer.send_activation_email(current_user) except Exception as e: app.logger.exception(e) return current_user
def edit_activity( activity_id, cancel, start_time=None, end_time=None, remove_end_time=False, context=None, ): with atomic_transaction(commit_at_end=True): reception_time = datetime.now() activity_to_update = Activity.query.get(activity_id) if not activity_to_update: raise AuthorizationError( f"Actor is not authorized to edit the activity") mission = Mission.query.options(selectinload(Mission.activities)).get( activity_to_update.mission_id) check_actor_can_log_on_mission_for_user_at( current_user, activity_to_update.user, mission, activity_to_update.start_time, ) if activity_to_update.is_dismissed: raise ResourceAlreadyDismissedError(f"Activity already dismissed.") db.session.add(activity_to_update) app.logger.info( f"{'Cancelling' if cancel else 'Revising'} {activity_to_update}") if cancel: activity_to_update.dismiss(reception_time, context) else: updates = {} if start_time: updates["start_time"] = start_time if end_time or remove_end_time: updates["end_time"] = end_time activity_to_update.revise(reception_time, revision_context=context, **updates) return activity_to_update
def mutate(cls, _, info, **comment_input): with atomic_transaction(commit_at_end=True): reception_time = datetime.now() mission_id = comment_input.get("mission_id") mission = Mission.query.get(mission_id) if not comment_input["text"]: raise InvalidParamsError("Cannot create an empty comment.") comment = Comment( submitter=current_user, mission=mission, text=comment_input["text"], reception_time=reception_time, ) db.session.add(comment) return comment
def mutate(cls, _, info, **data): with atomic_transaction(commit_at_end=True): user = create_user(**data) user.create_activation_link() try: mailer.send_activation_email(user) except Exception as e: app.logger.exception(e) tokens = create_access_tokens_for(user) @after_this_request def set_cookies(response): set_auth_cookies(response, user_id=user.id, **tokens) return response return UserTokens(**tokens)
def mutate(cls, _, info, **mission_input): with atomic_transaction(commit_at_end=True): app.logger.info( f"Creating a new mission with name {mission_input.get('name')}" ) # Preload resources company_id = mission_input.get("company_id") if company_id: company = Company.query.get(company_id) if not belongs_to_company_at( current_user, company, include_pending_invite=False): raise AuthorizationError( "Actor is not authorized to create a mission for the company" ) else: company = current_user.primary_company if not company: raise AuthorizationError("Actor has no primary company") context = mission_input.get("context") received_vehicle_id = mission_input.get("vehicle_id") received_vehicle_registration_number = mission_input.get( "vehicle_registration_number") vehicle = (find_or_create_vehicle( received_vehicle_id, received_vehicle_registration_number, company, ) if received_vehicle_id or received_vehicle_registration_number else None) mission = Mission( name=mission_input.get("name"), company=company, reception_time=datetime.now(), context=context, submitter=current_user, vehicle=vehicle, ) db.session.add(mission) return mission
def mutate( cls, _, info, mission_id, vehicle_id=None, vehicle_registration_number=None, ): with atomic_transaction(commit_at_end=True): mission = Mission.query.get(mission_id) if not vehicle_id and not vehicle_registration_number: app.logger.warning("No vehicle was associated to the mission") else: vehicle = find_or_create_vehicle(vehicle_id, vehicle_registration_number, mission.company) mission.vehicle_id = vehicle.id return mission.vehicle
def mutate( cls, _, info, authorization_code, original_redirect_uri, state, invite_token=None, create=False, ): with atomic_transaction(commit_at_end=True): fc_user_info, fc_token = get_fc_user_info(authorization_code, original_redirect_uri) user = get_user_from_fc_info(fc_user_info) if not create and not user: raise AuthenticationError("User does not exist") if create and user and user.email: # TODO : raise proper error raise FCUserAlreadyRegisteredError( "User is already registered") if not user: user = create_user( first_name=fc_user_info.get("given_name"), last_name=fc_user_info.get("family_name"), email=fc_user_info.get("email"), invite_token=invite_token, fc_info=fc_user_info, ) tokens = create_access_tokens_for(user) @after_this_request def set_cookies(response): set_auth_cookies(response, user_id=user.id, **tokens, fc_token=fc_token) return response return UserTokensWithFC(**tokens, fc_token=fc_token)
def mutate(self, _, info, employment_id): with atomic_transaction(commit_at_end=True): employment = Employment.query.get(employment_id) if not employment or not company_admin_at( current_user, employment.company ): raise AuthorizationError( "Actor is not authorized to cancel the employment" ) if employment.is_dismissed: raise InvalidResourceError( f"Could not find valid employment with id {employment_id}" ) app.logger.info(f"Cancelling Employment {employment_id}") employment.dismiss() return Void(success=True)
def mutate(cls, _, info, token): with atomic_transaction(commit_at_end=True): try: decoded_token = jwt.decode(token, app.config["JWT_SECRET_KEY"], algorithms=["HS256"]) user_id = decoded_token["user_id"] activation_token = decoded_token["token"] email = decoded_token["email"] expires_at = decoded_token["expires_at"] except Exception as e: raise InvalidTokenError(f"Token is invalid : {e}", should_alert_team=True) if expires_at < datetime.now().timestamp(): raise TokenExpiredError("Token has expired") try: user = User.query.get(user_id) current_activation_token = user.activation_email_token except Exception as e: raise InvalidTokenError("Invalid user in token", should_alert_team=True) if (email != user.email or not current_activation_token or activation_token != current_activation_token): raise InvalidTokenError( "Token is no more valid because it has been redeemed or a new token exists" ) user.has_activated_email = True @after_this_request def set_cookies(response): set_auth_cookies(response, **create_access_tokens_for(user), user_id=user.id) return response return user
def mutate(cls, _, info, token): user_to_auth = None with atomic_transaction(commit_at_end=True): employment = _get_employment_by_token(token) if employment.user: user_to_auth = employment.user employment.validate_by(user=user_to_auth) else: @with_authorization_policy(authenticated_and_active) def bind_and_redeem(): if employment.is_primary is None: employment.is_primary = ( current_user.primary_employment_at( employment.start_date ) is None ) employment.bind(current_user) employment.validate_by(user=current_user) bind_and_redeem() if user_to_auth: @after_this_request def set_cookies(response): set_auth_cookies( response, **create_access_tokens_for(user_to_auth), user_id=user_to_auth.id, ) return response return employment
def mutate(cls, _, info, token, password): with atomic_transaction(commit_at_end=True): try: decoded_token = jwt.decode(token, app.config["JWT_SECRET_KEY"], algorithms=["HS256"]) user_id = decoded_token["user_id"] current_password = decoded_token["hash"] expires_at = decoded_token["expires_at"] except Exception as e: raise InvalidTokenError(f"Token is invalid : {e}") if expires_at < datetime.now().timestamp(): raise TokenExpiredError("Token has expired") user = User.query.get(user_id) if not user: raise InvalidTokenError("Invalid user in token") if current_password != user.password: raise InvalidTokenError( "Token is no more valid because it has been redeemed or a new token exists" ) user.revoke_all_tokens() user.password = password @after_this_request def set_cookies(response): set_auth_cookies(response, **create_access_tokens_for(user), user_id=user.id) return response return user
def mutate(cls, _, info, mission_location_id, kilometer_reading): with atomic_transaction(commit_at_end=True): mission_location = LocationEntry.query.get(mission_location_id) mission_location.register_kilometer_reading(kilometer_reading) return Void(success=True)
def mutate(cls, _, info, mission_id, user_id=None): with atomic_transaction(commit_at_end=True): mission = Mission.query.get(mission_id) is_admin_validation = ( mission.company_id in current_user.current_company_ids_with_admin_rights) user = None if user_id: if not is_admin_validation and user_id != current_user.id: raise AuthorizationError( "Actor is not authorized to validate the mission for the user" ) user = User.query.get(user_id) if user: activities_to_validate = mission.activities_for(user) else: activities_to_validate = mission.acknowledged_activities if not activities_to_validate: raise NoActivitiesToValidateError( "There are no activities in the validation scope.") if any([not a.end_time for a in activities_to_validate]): raise MissionStillRunningError() mission_validation = MissionValidation( submitter=current_user, reception_time=datetime.now(), mission=mission, user=user if is_admin_validation else current_user, is_admin=is_admin_validation, ) db.session.add(mission_validation) try: if is_admin_validation: latest_non_admin_validations = [ v for v in mission.latest_validations_per_user if not v.is_admin ] if user: latest_user_validation = [ v for v in latest_non_admin_validations if v.submitter_id == user.id ] users_in_alerting_scope = ({ user: latest_user_validation[0] } if latest_user_validation else dict()) else: users_in_alerting_scope = { v.user: v for v in latest_non_admin_validations } for u, validation in users_in_alerting_scope.items(): validation_time = validation.reception_time all_user_activities = mission.activities_for( u, include_dismissed_activities=True) if any([ activity.last_update_time > validation_time for activity in all_user_activities ]): ( old_start_time, old_end_time, old_timers, ) = compute_aggregate_durations( activity_versions_at(all_user_activities, validation_time)) ( new_start_time, new_end_time, new_timers, ) = compute_aggregate_durations([ a for a in all_user_activities if not a.is_dismissed ]) if (old_start_time != new_start_time or old_end_time != new_end_time or old_timers["total_work"] != new_timers["total_work"]): try: mailer.send_warning_email_about_mission_changes( user=u, mission=mission, admin=current_user, old_start_time=old_start_time, new_start_time=new_start_time, old_end_time=old_end_time, new_end_time=new_end_time, old_timers=old_timers, new_timers=new_timers, ) except MailjetError as e: app.logger.exception(e) except Exception as e: app.logger.exception(e) return mission_validation
def mutate(cls, _, info, usual_name, siren, sirets): with atomic_transaction(commit_at_end=True): siren_api_info = None try: siren_api_info = siren_api_client.get_siren_info(siren) except Exception as e: app.logger.warning( f"Could not add SIREN API info for company of SIREN {siren} : {e}" ) require_kilometer_data = True main_activity_code = "" if siren_api_info: formatted_main_activity = main_activity_code = siren_api_info[ "uniteLegale"]["activitePrincipaleUniteLegale"] main_activity = (NafCode.get_code(main_activity_code) if main_activity_code else None) if main_activity: formatted_main_activity = ( f"{main_activity.code} {main_activity.label}") # For déménagement companies disable kilometer data by default if (siren_api_info["uniteLegale"] ["nomenclatureActivitePrincipaleUniteLegale"] == "NAFRev2" and main_activity_code == "49.42Z"): require_kilometer_data = False now = datetime.now() company = Company( usual_name=usual_name, siren=siren, sirets=sirets, siren_api_info=siren_api_info, allow_team_mode=True, require_kilometer_data=require_kilometer_data, ) db.session.add(company) db.session.flush() # Early check for SIREN duplication admin_employment = Employment( is_primary=False if current_user.primary_company else True, user_id=current_user.id, company=company, start_date=now.date(), validation_time=now, validation_status=EmploymentRequestValidationStatus.APPROVED, has_admin_rights=True, reception_time=now, submitter_id=current_user.id, ) db.session.add(admin_employment) app.logger.info( f"Signed up new company {company}", extra={ "post_to_mattermost": True, "log_title": "New company signup", "emoji": ":tada:", }, ) try: mailer.send_company_creation_email(company, current_user) except Exception as e: app.logger.exception(e) if app.config["INTEGROMAT_COMPANY_SIGNUP_WEBHOOK"]: # Call Integromat for Trello card creation try: first_establishment_info = (siren_api_info["etablissements"][0] if siren_api_info else None) response = requests.post( app.config["INTEGROMAT_COMPANY_SIGNUP_WEBHOOK"], data=dict( name=company.name, creation_time=company.creation_time, submitter_name=current_user.display_name, submitter_email=current_user.email, siren=company.siren, metabase_link= f"{app.config['METABASE_COMPANY_DASHBOARD_BASE_URL']}{company.id}", location= f"{first_establishment_info.get('adresse', '')} {first_establishment_info.get('codePostal', '')}" if first_establishment_info else None, activity_code=formatted_main_activity, n_employees=format_tranche_effectif( siren_api_info["uniteLegale"] ["trancheEffectifsUniteLegale"]), n_employees_year=siren_api_info["uniteLegale"] ["anneeEffectifsUniteLegale"], ), timeout=3, ) if not response.status_code == 200: app.logger.warning( f"Creation of Trello card for {company} failed with error : {response.text}" ) except Exception as e: app.logger.warning( f"Creation of Trello card for {company} failed with error : {e}" ) return CompanySignUp(company=company, employment=admin_employment)
def mutate(cls, _, info, **employment_input): employment_is_primary = employment_input.get("is_primary") force_employment_type = employment_is_primary is not None with atomic_transaction(commit_at_end=True): reception_time = datetime.now() app.logger.info( f"Creating employment submitted by {current_user} for User {employment_input.get('user_id', employment_input.get('mail'))} in Company {employment_input['company_id']}" ) company = Company.query.get(employment_input["company_id"]) if (employment_input.get("user_id") is None) == ( employment_input.get("mail") is None ): raise InvalidParamsError( "Exactly one of userId or mail should be provided." ) user_id = employment_input.get("user_id") user = None invite_token = uuid4().hex if user_id: user = User.query.options(selectinload(User.employments)).get( employment_input["user_id"] ) if not user: raise InvalidResourceError( "Invalid user id", should_alert_team=False ) start_date = employment_input.get("start_date", date.today()) user_current_primary_employment = None if user: user_current_primary_employment = user.primary_employment_at( start_date ) if ( not user_current_primary_employment and force_employment_type and not employment_is_primary ): raise MissingPrimaryEmploymentError( "Cannot create a secondary employment for user because there is no primary employment in the same period" ) if not force_employment_type and user: employment_is_primary = user_current_primary_employment is None employment = Employment( reception_time=reception_time, submitter=current_user, is_primary=employment_is_primary, validation_status=EmploymentRequestValidationStatus.PENDING, start_date=start_date, end_date=employment_input.get("end_date"), company=company, has_admin_rights=employment_input.get("has_admin_rights"), user_id=user_id, invite_token=invite_token, email=employment_input.get("mail"), ) db.session.add(employment) try: mailer.send_employee_invite( employment, user.email if user else employment_input.get("mail"), ) except MailjetError as e: if not user: raise e return employment
def mutate( cls, _, info, mission_id, type, company_known_address_id=None, geo_api_data=None, manual_address=None, kilometer_reading=None, ): with atomic_transaction(commit_at_end=True): if (int(company_known_address_id is not None) + int(geo_api_data is not None) + int(manual_address is not None) != 1): raise InvalidParamsError( "Exactly one of companyKnownAddressId or geoApiData or manualAddress should be set" ) mission = Mission.query.get(mission_id) company_known_address = None if company_known_address_id: company_known_address = CompanyKnownAddress.query.get( company_known_address_id) if (not company_known_address or company_known_address.company_id != mission.company_id): raise InvalidParamsError("Invalid companyKnownAddressId") address = company_known_address.address elif geo_api_data: address = Address.get_or_create(geo_api_data) else: address = Address(manual=True, name=manual_address) db.session.add(address) existing_location_entry = [ l for l in mission.location_entries if l.type == type ] existing_location_entry = (existing_location_entry[0] if existing_location_entry else None) if existing_location_entry: are_addresses_equal = ( address.name == existing_location_entry.address.name if address.manual and existing_location_entry.address.manual else address == existing_location_entry.address) if are_addresses_equal: if kilometer_reading: existing_location_entry.register_kilometer_reading( kilometer_reading) return existing_location_entry raise MissionLocationAlreadySetError() now = datetime.now() location_entry = LocationEntry( _address=address, mission=mission, reception_time=now, submitter=current_user, _company_known_address=company_known_address, type=type, ) location_entry.register_kilometer_reading(kilometer_reading, now) db.session.add(location_entry) return location_entry