Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit e9662a2

Browse files
committed
Merge branch 'main' into Ajay/milestone-3-migration
2 parents ea7582c + 9c91ee2 commit e9662a2

File tree

15 files changed

+274
-169
lines changed

15 files changed

+274
-169
lines changed

codecov/commands/base.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1+
from django.conf import settings
12
from django.contrib.auth.models import AnonymousUser
23

3-
from codecov.commands.exceptions import MissingService
4+
import services.self_hosted as self_hosted
5+
from codecov.commands.exceptions import (
6+
MissingService,
7+
Unauthenticated,
8+
Unauthorized,
9+
ValidationError,
10+
)
11+
from codecov_auth.helpers import current_user_part_of_org
412
from codecov_auth.models import Owner, User
13+
from core.models import Repository
514

615

716
class BaseCommand:
@@ -44,3 +53,59 @@ def __init__(self, current_owner: Owner, service: str, current_user: User = None
4453

4554
if self.current_owner:
4655
self.current_user = self.current_owner.user
56+
57+
def ensure_is_admin(self, owner: Owner) -> None:
58+
"""
59+
Ensures that the `current_owner` is an admin of `owner`,
60+
or raise `Unauthorized` otherwise.
61+
"""
62+
63+
if not current_user_part_of_org(self.current_owner, owner):
64+
raise Unauthorized()
65+
66+
if settings.IS_ENTERPRISE:
67+
if not self_hosted.is_admin_owner(self.current_owner):
68+
raise Unauthorized()
69+
else:
70+
if not owner.is_admin(self.current_owner):
71+
raise Unauthorized()
72+
73+
def resolve_owner_and_repo(
74+
self,
75+
owner_username: str,
76+
repo_name: str,
77+
ensure_is_admin: bool = False,
78+
only_viewable: bool = False,
79+
only_active: bool = False,
80+
) -> tuple[Owner, Repository]:
81+
"""
82+
Resolves the `Owner` and `Repository` based on the passed `owner_username`
83+
and `repo_name` respectively.
84+
85+
If `ensure_is_admin` is set, this will also ensure that the `current_owner` is an
86+
admin on the resolved `Owner`.
87+
"""
88+
if ensure_is_admin and not self.current_user.is_authenticated:
89+
raise Unauthenticated()
90+
91+
owner = Owner.objects.filter(
92+
service=self.service, username=owner_username
93+
).first()
94+
95+
if not owner:
96+
raise ValidationError("Owner not found")
97+
98+
if ensure_is_admin:
99+
self.ensure_is_admin(owner)
100+
101+
repo_query = Repository.objects
102+
if only_viewable:
103+
repo_query = repo_query.viewable_repos(self.current_owner)
104+
if only_active:
105+
repo_query = repo_query.filter(active=True)
106+
107+
repo = repo_query.filter(author=owner, name=repo_name).first()
108+
if not repo:
109+
raise ValidationError("Repo not found")
110+
111+
return (owner, repo)

codecov_auth/tests/unit/views/test_base.py

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime, timedelta
1+
from datetime import datetime, timedelta, timezone
22
from unittest.mock import Mock, patch
33

44
import pytest
@@ -8,10 +8,15 @@
88
from django.http import HttpResponse
99
from django.test import RequestFactory, TestCase, override_settings
1010
from freezegun import freeze_time
11-
from shared.django_apps.codecov_auth.tests.factories import OwnerFactory, UserFactory
11+
from shared.django_apps.codecov_auth.tests.factories import (
12+
OwnerFactory,
13+
SessionFactory,
14+
UserFactory,
15+
)
1216
from shared.license import LicenseInformation
1317

14-
from codecov_auth.models import Owner, OwnerProfile
18+
from codecov_auth.models import DjangoSession, Owner, OwnerProfile, Session
19+
from codecov_auth.tests.factories import DjangoSessionFactory
1520
from codecov_auth.views.base import LoginMixin, StateMixin
1621

1722

@@ -729,3 +734,118 @@ def test_login_authenticated_with_claimed_owner(self):
729734
# does not re-claim owner
730735
assert owner.user is not None
731736
assert owner.user != user
737+
738+
@patch("services.refresh.RefreshService.trigger_refresh", lambda *args: None)
739+
def test_login_owner_with_expired_login_session(self):
740+
user = UserFactory()
741+
owner = OwnerFactory(service="github", user=user)
742+
743+
another_user = UserFactory()
744+
another_owner = OwnerFactory(service="github", user=another_user)
745+
746+
now = datetime.now(timezone.utc)
747+
748+
# Create a session that will be deleted
749+
to_be_deleted_1 = SessionFactory(
750+
owner=owner,
751+
type="login",
752+
name="to_be_deleted",
753+
lastseen="2021-01-01T00:00:00+00:00",
754+
login_session=DjangoSessionFactory(expire_date=now - timedelta(days=1)),
755+
)
756+
to_be_deleted_1_session_key = to_be_deleted_1.login_session.session_key
757+
758+
# Create a session that will not be deleted because its not a login session
759+
to_be_kept_1 = SessionFactory(
760+
owner=owner,
761+
type="api",
762+
name="to_be_kept",
763+
lastseen="2021-01-01T00:00:00+00:00",
764+
login_session=DjangoSessionFactory(expire_date=now + timedelta(days=1)),
765+
)
766+
767+
# Create a session that will not be deleted because it's not expired
768+
to_be_kept_2 = SessionFactory(
769+
owner=owner,
770+
type="login",
771+
name="to_be_kept",
772+
lastseen="2021-01-01T00:00:00+00:00",
773+
login_session=DjangoSessionFactory(expire_date=now + timedelta(days=1)),
774+
)
775+
776+
# Create a session that will not be deleted because it's not the owner's session
777+
to_be_kept_3 = SessionFactory(
778+
owner=another_owner,
779+
type="login",
780+
name="to_be_kept",
781+
lastseen="2021-01-01T00:00:00+00:00",
782+
login_session=DjangoSessionFactory(expire_date=now - timedelta(seconds=1)),
783+
)
784+
785+
assert (
786+
len(DjangoSession.objects.filter(session_key=to_be_deleted_1_session_key))
787+
== 1
788+
)
789+
assert (
790+
len(
791+
DjangoSession.objects.filter(
792+
session_key=to_be_kept_1.login_session.session_key
793+
)
794+
)
795+
== 1
796+
)
797+
assert (
798+
len(
799+
DjangoSession.objects.filter(
800+
session_key=to_be_kept_2.login_session.session_key
801+
)
802+
)
803+
== 1
804+
)
805+
assert (
806+
len(
807+
DjangoSession.objects.filter(
808+
session_key=to_be_kept_3.login_session.session_key
809+
)
810+
)
811+
== 1
812+
)
813+
814+
self.request.user = user
815+
self.mixin_instance.login_owner(owner, self.request, HttpResponse())
816+
owner.refresh_from_db()
817+
818+
new_login_session = Session.objects.filter(name=None)
819+
820+
assert len(new_login_session) == 1
821+
assert len(Session.objects.filter(name="to_be_deleted").all()) == 0
822+
assert len(Session.objects.filter(name="to_be_kept").all()) == 3
823+
824+
assert (
825+
len(DjangoSession.objects.filter(session_key=to_be_deleted_1_session_key))
826+
== 0
827+
)
828+
assert (
829+
len(
830+
DjangoSession.objects.filter(
831+
session_key=to_be_kept_1.login_session.session_key
832+
)
833+
)
834+
== 1
835+
)
836+
assert (
837+
len(
838+
DjangoSession.objects.filter(
839+
session_key=to_be_kept_2.login_session.session_key
840+
)
841+
)
842+
== 1
843+
)
844+
assert (
845+
len(
846+
DjangoSession.objects.filter(
847+
session_key=to_be_kept_3.login_session.session_key
848+
)
849+
)
850+
== 1
851+
)

0 commit comments

Comments
 (0)