import rules import datetime from django.utils import timezone # PERMISSIONS # USER permissions """ USER permissions - There are 2 types of user: basic, pro - These methods exist on user - user.rower - user.is_anonymous - user.is_staff - These methods exist on rower - rower.rowerplan - rower.protrialexpires - rower.protrialexpires - rower.mycoachgroup - rower.coachinggroups - rower.team - These Rules apply to Rowers - Pro can have any number of groups - test_permissions.PermissionFreeCoach.test_coach_groupmanager - test_permissions.PermissionsBasicsTests.test_coach_groupmanager - test_permissions.PermissionsViewTests.test_coach_groups_create - (REMOVE) Pro and Plan users can have one group and not more - test_permissions.PermissionsFreeCoach.test_pro_groupmanager - test_permissions.PermissionsViewTests.test_pro_groups_create - (REMOVE) Free Coach user cannot have workouts - test_permissions.PermissionFreeCoach.test_add_workout_freecoach - (REMOVE) Free coach can create more than one group - test_permissions.PermissionsFreeCoach.test_plan_groupmanager - (ADJUST) Free Coach & Plan (Self Coach) can create planned sessions and team planned sessions - test_permissions.PermissionsFreeCoach.test_plan_create_session - test_permissions.PermissionsFreeCoach.test_coach_create_session - (REMOVE) Pro cannot create planned sessions or team planned sessions - test_permissions.PermissionsViewTests.test_pro_create_session - (REMOVE) Basic cannot join groups led by Free Coach - test_permissions.PermissionFreeCoach.test_add_basic_pro_or_plan - (ADJUST) Coach can edit athlete settings (if in coachinggroup) - test_permissions.PermissionsViewTests.test_coach_edit_athlete_prefs - test_permissions.PermissionsViewTests.test_coach_edit_athlete_prefs_not - test_permissions.PermissionsViewTests.test_coach_edit_athlete_settings - test_permissions.PermissionsViewTests.test_coach_edit_athlete_settings_not - test_permissions.PermissionsViewTests.test_coach_edit_athlete_account - test_permissions.PermissionsViewTests.test_coach_edit_athlete_account_not - test_permissions.PermissionsViewTests.test_coach_edit_athlete_exportsettings - test_permissions.PermissionsViewTests.test_coach_edit_athlete_exportsettings_not - (ADJUST) Coach can upload a workout on behalf of athlete - test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload - test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload_not - (REMOVE) Pro and Plan cannot upload a workout on behalf of athlete - test_permissions.PermissionsViewTests.test_plan_edit_athlete_upload - (ADJUST) Coach can run analytics for athlete - test_permissions.PermissionsViewTests.test_coach_edit_athlete_analysis - test_permissions.PermissionsViewTests.test_coach_edit_athlete_analysis_not - test_permissions.PermissionsViewTests.test_pro_edit_athlete_analysis - (ADJUST) Pro and Plan user cannot run analysis for members of their groups - test_permissions.PermissionsViewTests.test_plan_edit_athlete_analysis - (ADJUST) Pro and Plan user cannot edit athlete sessings - test_permissions.PermissionsViewTests.test_plan_edit_athlete_settings - test_permissions.PermissionsViewTests.test_pro_edit_athlete_settings - (REMOVE) Self-Coach and Pro cannot create more than one group - test_permissions.PermissionsFreeCoach.test_plan_groupmanager - Pro users and higher can join group led by other Pro or higher user - test_permissions.PermissionsFreeCoach.test_add_proplan_pro_or_plan - (ADJSUT) Pro or basic cannot create planned sessions or team planned sessions - test_permissions.PermissionsFreeCoach.test_pro_create_plannedsession """ # used in can_plan_user @rules.predicate def user_is_not_basic(user): if user.rower.rowerplan != 'basic': return True if user.rower.protrialexpires >= timezone.now().date(): return True # pragma: no cover if user.rower.protrialexpires >= timezone.now().date(): return True # pragma: no cover if user.rower.coachtrialexpires >= timezone.now().date(): return True # pragma: no cover return False @rules.predicate def user_is_coachee(user): coaches = user.rower.get_coaches() for coach in coaches: if coach.rowerplan == 'pro': return True if coach.protrialexpires >= timezone.now().date(): return True return False @rules.predicate def user_is_basic(user): return not user_is_not_basic(user) @rules.predicate def can_start_trial(user): if user.is_anonymous: # pragma: no cover return False return user.rower.protrialexpires == datetime.date(1970, 1, 1) @rules.predicate def can_start_plantrial(user): if user.is_anonymous: # pragma: no cover return False return user.rower.protrialexpires == datetime.date(1970, 1, 1) @rules.predicate def can_start_coachtrial(user): if user.is_anonymous: # pragma: no cover return False return user.rower.protrialexpires == datetime.date(1970, 1, 1) @rules.predicate def is_staff(user): # pragma: no cover return user.is_staff @rules.predicate def is_coach(user): if user is None: return False if user.is_anonymous: return False if user.rower.protrialexpires >= timezone.now().date(): return True return user.rower.rowerplan == 'pro' def is_paid_coach(user): if user.rower.protrialexpires >= timezone.now().date(): return True return user.rower.rowerplan == 'pro' @rules.predicate def is_planmember(user): try: r = user.rower except AttributeError: # pragma: no cover return False if user.rower.protrialexpires >= timezone.now().date(): return True return r.rowerplan == 'pro' @rules.predicate def is_promember(user): try: r = user.rower except AttributeError: return False return r.rowerplan == 'pro' @rules.predicate def is_protrial(user): try: r = user.rower except AttributeError: return False if r.rowerplan == 'basic': return r.protrialexpires >= timezone.now().date() return False # pragma: no cover @rules.predicate def is_coachtrial(user): try: r = user.rower except AttributeError: return False if r.rowerplan == 'basic': return r.protrialexpires >= timezone.now().date() return False # pragma: no cover ispromember = is_promember | is_protrial | is_coach | is_coachtrial can_have_teams = ispromember | is_coach | is_coachtrial @rules.predicate def can_add_team(user): if is_coach(user): return True if ispromember(user) or is_planmember(user): otherteams = user.rower.get_managed_teams() if otherteams.count() == 0: return True if is_coachtrial(user): return True return False @rules.predicate def can_add_plan(user): return ispromember(user) @rules.predicate def can_add_workout(user): if user.is_anonymous: # pragma: no cover return False return True @rules.predicate def is_plantrial(user): try: r = user.rower except AttributeError: # pragma: no cover return False if r.rowerplan == 'basic': return r.protrialexpires >= timezone.now().date() return False # pragma: no cover isplanmember = is_planmember | is_plantrial iscoachmember = is_coachtrial | is_coach @rules.predicate def can_add_session(user): return isplanmember(user) or is_coach(user) or is_coachtrial(user) # User / Coach relationships (Rower object) @rules.predicate def can_plan(user): if user.is_anonymous: # pragma: no cover return False if user.rower.rowerplan == 'pro': return True if user.rower.rowerplan == 'basic': return user.rower.protrialexpires >= timezone.now().date() or user.rower.protrialexpires >= timezone.now().date() # checks if rower is coach of user (or is user himself) @rules.predicate def is_coach_user(usercoach, userrower): if usercoach == userrower: return True if not is_coach(usercoach): return False r = userrower.rower coaches = [] for group in r.coachinggroups.all(): newcoaches = group.get_coaches() for coach in newcoaches: coaches.append(coach) for coach in coaches: if usercoach.rower == coach: return True return False # checks if rower is coach of user (or is user himself) @rules.predicate def is_anonymous_or_coach(usercoach, userrower): # pragma: no cover if usercoach == userrower: return True if userrower.is_anonymous and userrower.is_anonymous: return True if not is_coach(usercoach): return False r = userrower.rower coaches = [] for group in r.coachinggroups.all(): newcoaches = group.get_coaches() for coach in newcoaches: coaches.append(coach) for coach in coaches: if usercoach.rower == coach: return True return False # check if rower and user are members of the same team @rules.predicate def is_rower_team_member(user, rower): if user.rower == rower: # pragma: no cover return True if is_coach_user(user, rower.user): return True teams = rower.team.all() for team in teams: if team.private == 'open': if team in user.rower.team.all(): return True if team.manager == user: return True return False @rules.predicate def can_add_workout_member(user, rower): if not user: # pragma: no cover return False if user.is_anonymous: # pragma: no cover return False if user == rower.user: # pragma: no cover return True # only below tested - need test user == rower.user return is_coach(user) and user.rower in rower.get_coaches() # check if user can plan for the rower @rules.predicate def can_plan_user(user, rower): # user must have planning permission if not can_plan(user): return False # if has planning permission, can always plan for himself if rower == user.rower: return True teams = user.rower.get_managed_teams() # free coach, plan etc cannot plan for basic if not is_paid_coach(user) and user_is_not_basic(user): for t in teams: if rower in t.rower.all(): # pragma: no cover return True # paying coach can plan for all kinds of rowers if is_paid_coach(user): for t in teams: if rower in t.rower.all(): return True return False rules.add_perm('rower.add_plan', can_plan_user) # replaces checkaccessplanuser rules.add_perm('rower.is_coach', is_coach_user) # replaces checkaccessuser rules.add_perm('rower.is_pro', ispromember) rules.add_perm('rower.is_staff', is_staff) # WORKOUT permissions """ WORKOUT permissions - These methods exist on Workout - workout.privacy - These rules apply to workouts - User can add, delete and change their own workouts - test_aworkouts - Coach can add and change workouts for their athletes, but not delete - test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload - test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload_not - Basic, Pro and Plan user can view but cannot add or change an athlete's workout - test_permissions.PermissionsViewTests.test_coach_edit_athlete_workout - test_permissions.PermissionsViewTests.test_plan_edit_athlete_upload - test_permissions.PermissionsViewTests.test_plan_edit_athlete_workout - test_permissions.PermissionsViewTests.test_basic_edit_athelte_workout - Anonymous users can view team members' workout (but not see list of workouts) - test_aworkouts - Rules for Workouts are transferred to objects related to the Workout, - Charts - Video Analysis - WorkoutComment? """ # check if user is owner or coach of owner of workout @rules.predicate def is_workout_owner(user, workout): if user.is_anonymous: return False try: r = user.rower except AttributeError: # pragma: no cover return False return workout.user.user == user @rules.predicate def is_workout_user(user, workout): if user.is_anonymous: return False try: r = user.rower except AttributeError: # pragma: no cover return False if workout.privacy == 'hidden': return user == workout.user.user if workout.workoutsource == 'strava': return user == workout.user.user if workout.user == r: return True return is_coach_user(user, workout.user.user) # check if user is in same team as owner of workout @rules.predicate def workout_is_strava(workout): return workout.workoutsource == 'strava' @rules.predicate def is_workout_team(user, workout): if user.is_anonymous: # pragma: no cover return False try: r = user.rower except AttributeError: # pragma: no cover return False if workout.privacy == 'hidden': return user == workout.user.user if workout.workoutsource == 'strava': return user == workout.user.user if workout.user == r: return True return is_rower_team_member(user, workout.user) # check if user can see workout @rules.predicate def can_view_workout(user, workout): if workout.workoutsource == 'strava': return user == workout.user.user if workout.privacy not in ('hidden', 'private'): return True if user.is_anonymous: # pragma: no cover return False return user == workout.user.user # pragma: no cover can_change_workout = is_workout_user # replaces checkworkoutuser rules.add_perm('workout.change_workout', can_change_workout) # replaces checkworkoutuserview rules.add_perm('workout.view_workout', can_view_workout) rules.add_perm('workout.is_owner', is_workout_owner) # checkviewworkouts # PLANNING permissions """ - These rules apply to planning - Free coach can create planned sessions and team planned sessions - test_permissions.PermissionsViewTests.test_coach_create_session - Self coach and higher can create planned sessions and team planned sessions - test_permissions.PermissionsFreeCoach.test_plan_create_session - test_permissions.PermissionsFreeCoach.test_coach_create_session - Coach can create planned sessions and team planned sessions - test_permissions.PermissionsFreeCoach.test_plan_create_session - test_permissions.PermissionsFreeCoach.test_coach_create_session - Self Coach and higher can create planned sessions and team planned sessions - test_permissions.PermissionsViewTests.test_plan_create_session - Pro or Basic cannot create planned sessions or team planned sessions - test_permissions.PermissionsFreeCoach.test_pro_create_plannedsession - test_permissions.PermissionsFreeCoach.test_basic_create_plannedsession - WHO can comment (plannedsession_comment_view) - Only Session manager can change session - Strict View rules (stricter than workouts) - Basic user cannot manage a training plan - TrainingTarget - rules for view, add, change, delete - TrainingPlan - rules for view, add, change, delete - Cycle - inherits rules from TrainingPlan - PlannedSession - rules for view, add, change, delete, clone, save as template - check team - Special rules for Race (cannot be edited ex post) """ # Training Target rules # untested can_view_target to can_delete_target @rules.predicate def can_view_target(user, target): # pragma: no cover if user.is_anonymous: return False if user == target.manager.user: return True # a target's coach can view as well if is_coach_user(user, target.manager.user): return True # the object can view as well if user.rower in target.rowers.all(): return True @rules.predicate def can_change_target(user, target): # pragma: no cover if user.is_anonymous: return False return user == target.manager.user @rules.predicate def can_delete_target(user, target): if user.is_anonymous: # pragma: no cover return False return user == target.manager.user rules.add_perm('target.view_target', can_view_target) rules.add_perm('target.change_target', can_change_target) rules.add_perm('target.delete_target', can_delete_target) @rules.predicate def can_view_plan(user, plan): if user.is_anonymous: # pragma: no cover return False if user == plan.manager.user: return True # a plan's coach can view as well # below untested if is_coach_user(user, plan.manager.user): # pragma: no cover return True # the object can view as well if user.rower in plan.rowers.all(): # pragma: no cover return True @rules.predicate def can_change_plan(user, plan): if user.is_anonymous: # pragma: no cover return False return user == plan.manager.user # pragma: no cover # below untested @rules.predicate def can_delete_plan(user, plan): if user.is_anonymous: # pragma: no cover return False return user == plan.manager.user rules.add_perm('plan.view_plan', can_view_plan) rules.add_perm('plan.change_plan', can_change_plan) rules.add_perm('plan.delete_plan', can_delete_plan) rules.add_perm('plan.can_add_plan', can_add_plan) # untested @rules.predicate def can_view_cycle(user, cycle): # pragma: no cover try: return can_view_cycle(user, cycle.plan) except AttributeError: return can_view_plan(user, cycle.plan) return False @rules.predicate def can_change_cycle(user, cycle): # pragma: no cover try: return can_change_cycle(user, cycle.plan) except AttributeError: return can_change_plan(user, cycle.plan) return False @rules.predicate def can_delete_cycle(user, cycle): # pragma: no cover try: return can_delete_cycle(user, cycle.plan) except AttributeError: return can_delete_plan(user, cycle.plan) return False rules.add_perm('cycle.view_cycle', can_view_cycle) rules.add_perm('cycle.change_cycle', can_change_cycle) rules.add_perm('cycle.delete_cycle', can_delete_cycle) # check if user has view access to session @rules.predicate def can_view_session(user, session): if session.sessiontype in ['race', 'indoorrace']: # pragma: no cover return True if user.is_anonymous: # pragma: no cover return False # session manager can view session if user == session.manager: return True # if you're a rower in the session you can view it # below untested if user.rower in session.rower.all(): # pragma: no cover return True # coach users can view sessions created by their team members # below untested if is_coach(user) or is_coachtrial(user): # pragma: no cover teams = user.rower.get_managed_teams() for t in teams: teamusers = [member.u for member in t.rower.all()] if session.manager in teamusers: return True return False # pragma: no cover @rules.predicate def can_change_session(user, session): if user.is_anonymous: # pragma: no cover return False # session part of a race should not be changed through the session interface if session.sessiontype in ['race', 'indoorrace']: # pragma: no cover return False if user == session.manager: return True return False # pragma: no cover @rules.predicate def can_delete_session(user, session): # pragma: no cover if user.is_anonymous: return False if session.sessiontype in ['race', 'indoorrace']: return False if user == session.manager: return True return False rules.add_perm('plannedsession.add_session', can_add_session) rules.add_perm('plannedsession.view_session', can_view_session) rules.add_perm('plannedsession.change_session', can_change_session) rules.add_perm('plannedsession.delete_session', can_delete_session) # TEAM (group) permissions """ - These methods exist on team - team.manager - team.private - These rules apply to a team - A Pro or Plan rower can be manager of only 1 team - test_permissions.PermissionsFreeCoach.test_pro_groupmanager - test_permissions.PermissionsViewTests.test_pro_groups_create - A coach can have any number of teams (as manager) - test_permissions.PermissionsBasicTest.test_coach_groupmanager - Basic user cannot manage a group - test_permissions.PermissionsFreeCoach.test_basic_groupmanager - Basic user cannot join groups led by Free Coach or Pro - test_permissions.PermissionFreeCoach.test_add_basic_pro_or_plan - Basic athletes can be member of Coach led group - test_permissions.PermissionsBasicTest.test_add_coach - Pro users (and higher) can join team led by other Pro (or higher) user - test_permissions.PermissionsFreeCoach.test_add_proplan_pro_or_plan - test_permissions.PermissionsViewTests.test_team_member_request_pro_pro - test_permissions.PermissionsViewTests.test_team_member_request_basic_pro - On downgrade, Coach users lose all but their oldest team (add test!) """ # check if user is manager of the team @rules.predicate def is_team_manager(user, team): return team.manager == user # check is user is member of team - untested @rules.predicate def is_team_member(user, team): # pragma: no cover members = team.rower.all() return user in [member.user for member in members] # check if user can view team @rules.predicate def can_view_team(user, team): # user based - below untested if team.manager.rower.protrialexpires >= timezone.now().date(): return True if user.rower.rowerplan == 'basic' and team.manager.rower.rowerplan != 'pro': # pragma: no cover return is_plantrial(user) or is_protrial(user) or is_coachtrial(user) # team is public if team.private == 'open': return True # team is private - below untested return is_team_member(user, team) | is_team_manager(user, team) # pragma: no cover @rules.predicate def can_change_team(user, team): return is_team_manager(user, team) @rules.predicate def can_delete_team(user, team): return is_team_manager(user, team) @rules.predicate def can_join_team(user, team): return is_paid_coach(team.manager) or ispromember(user) # For Team functionality rules.add_perm('teams.view_team', can_view_team) rules.add_perm('teams.add_team', user_is_not_basic) rules.add_perm('teams.change_team', can_change_team) rules.add_perm('teams.delete_team', can_delete_team) # RACING permissions """ - VirtualRace - rules to add, view, delete, change - GeoCourse - rules to add, view, delete, change - RaceLogo """ @rules.predicate def can_change_course(user, course): if user.is_anonymous: # pragma: no cover return False return course.manager == user.rower # untested @rules.predicate def can_delete_course(user, course): if user.is_anonymous: # pragma: no cover return False return course.manager == user.rower @rules.predicate def can_delete_logo(user, logo): if user.is_anonymous: # pragma: no cover return False return logo.user == user # pragma: no cover @rules.predicate def can_change_race(user, race): if user.is_anonymous: # pragma: no cover return False return race.manager == user # everybody can view or add a course rules.add_perm('course.change_course', can_change_course) rules.add_perm('course.delete_course', can_delete_course) rules.add_perm('racelogo.delete_logo', can_delete_logo) # everybody can view a race rules.add_perm('virtualevent.change_race', can_change_race) # can races be deleted? # ANALYSIS permissions """ - Conditions - Alerts - rules to add, view, delete, change - cpdata """