Repository: readthedocs/readthedocs.org

Issue:
`RemoteRepository.admin` is not set properly on GitLab
When syncing `RemoteRepository` we are not setting `admin` field properly. We only set it to `True` when the user is the owner of the repository. It could be that the user is "Maintainer" (full access) but not owner.

The code that needs to be changed is at

https://github.com/readthedocs/readthedocs.org/blob/d2157c8c8ea75716d945d6988aa47d82e283ab4a/readthedocs/oauth/services/gitlab.py#L182-L184

GitLab is returning the `permissions` as:

```
"permissions": {"project_access": {"access_level": 40, "notification_level": 3}
```

https://docs.gitlab.com/ee/api/access_requests.html


Discussion:

Resolving patch:
diff --git a/readthedocs/oauth/services/gitlab.py b/readthedocs/oauth/services/gitlab.py
--- a/readthedocs/oauth/services/gitlab.py
+++ b/readthedocs/oauth/services/gitlab.py
@@ -41,6 +41,10 @@ class GitLabService(Service):
         re.escape(urlparse(adapter.provider_base_url).netloc),
     )
 
+    PERMISSION_NO_ACCESS = 0
+    PERMISSION_MAINTAINER = 40
+    PERMISSION_OWNER = 50
+
     def _get_repo_id(self, project):
         # The ID or URL-encoded path of the project
         # https://docs.gitlab.com/ce/api/README.html#namespaced-path-encoding
@@ -132,13 +136,20 @@ def sync_organizations(self):
 
         return remote_organizations, remote_repositories
 
-    def is_owned_by(self, owner_id):
-        return self.account.extra_data['id'] == owner_id
-
     def create_repository(self, fields, privacy=None, organization=None):
         """
         Update or create a repository from GitLab API response.
 
+        ``admin`` field is computed using the ``permissions`` fields from the
+        repository response. The permission from GitLab is given by an integer:
+          * 0: No access
+          * (... others ...)
+          * 40: Maintainer
+          * 50: Owner
+
+        https://docs.gitlab.com/ee/api/access_requests.html
+        https://gitlab.com/help/user/permissions
+
         :param fields: dictionary of response data from API
         :param privacy: privacy level to support
         :param organization: remote organization to associate with
@@ -179,9 +190,17 @@ def create_repository(self, fields, privacy=None, organization=None):
             else:
                 repo.clone_url = fields['http_url_to_repo']
 
-            repo.admin = not repo_is_public
-            if not repo.admin and 'owner' in fields:
-                repo.admin = self.is_owned_by(fields['owner']['id'])
+            project_access_level = group_access_level = self.PERMISSION_NO_ACCESS
+            project_access = fields.get('permissions', {}).get('project_access', {})
+            if project_access:
+                project_access_level = project_access.get('access_level', self.PERMISSION_NO_ACCESS)
+            group_access = fields.get('permissions', {}).get('group_access', {})
+            if group_access:
+                group_access_level = group_access.get('access_level', self.PERMISSION_NO_ACCESS)
+            repo.admin = any([
+                project_access_level in (self.PERMISSION_MAINTAINER, self.PERMISSION_OWNER),
+                group_access_level in (self.PERMISSION_MAINTAINER, self.PERMISSION_OWNER),
+            ])
 
             repo.vcs = 'git'
             repo.account = self.account


Test patch:
diff --git a/readthedocs/rtd_tests/tests/test_oauth.py b/readthedocs/rtd_tests/tests/test_oauth.py
--- a/readthedocs/rtd_tests/tests/test_oauth.py
+++ b/readthedocs/rtd_tests/tests/test_oauth.py
@@ -951,12 +951,10 @@ def get_private_repo_data(self):
         return data
 
     def test_make_project_pass(self):
-        with mock.patch('readthedocs.oauth.services.gitlab.GitLabService.is_owned_by') as m:  # yapf: disable
-            m.return_value = True
-            repo = self.service.create_repository(
-                self.repo_response_data, organization=self.org,
-                privacy=self.privacy,
-            )
+        repo = self.service.create_repository(
+            self.repo_response_data, organization=self.org,
+            privacy=self.privacy,
+        )
         self.assertIsInstance(repo, RemoteRepository)
         self.assertEqual(repo.name, 'testrepo')
         self.assertEqual(repo.full_name, 'testorga / testrepo')
@@ -1009,9 +1007,7 @@ def test_make_private_project(self):
         """
         data = self.repo_response_data.copy()
         data['visibility'] = 'public'
-        with mock.patch('readthedocs.oauth.services.gitlab.GitLabService.is_owned_by') as m:  # yapf: disable
-            m.return_value = True
-            repo = self.service.create_repository(data, organization=self.org)
+        repo = self.service.create_repository(data, organization=self.org)
         self.assertIsNotNone(repo)
 
     @mock.patch('readthedocs.oauth.services.gitlab.log')