Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get project's owners and permissions of various users #101

Merged
merged 5 commits into from
Jul 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion ogr/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

import datetime
from enum import IntEnum
from typing import Optional, Match, List, Dict
from typing import Optional, Match, List, Dict, Set
from urllib.request import urlopen

from ogr.parsing import parse_git_repo
Expand Down Expand Up @@ -359,6 +359,44 @@ def get_fork(self, create: bool = True) -> Optional["GitProject"]:
"""
raise NotImplementedError()

def get_owners(self) -> List[str]:
"""
Get all project owners
:return: List of usernames
"""
raise NotImplementedError()

def who_can_close_issue(self) -> Set[str]:
"""
Get all usernames who have permissions to modify an Issue
:return: Set of usernames
"""
raise NotImplementedError()

def who_can_merge_pr(self) -> Set[str]:
"""
Get all usernames who have permissions to modify a PR
:return: Set of usernames
"""
raise NotImplementedError()

def can_close_issue(self, username: str, issue: Issue) -> bool:
"""
Check if user have permissions to modify an Issue
:param username: str
:param issue: Issue
:return: true if user can close issue, false otherwise
"""
raise NotImplementedError()

def can_merge_pr(self, username) -> bool:
"""
Check if user have permissions to modify an Pr
:param username: str
:return: true if user can close PR, false otherwise
"""
raise NotImplementedError()

def get_issue_list(self, status: IssueStatus = IssueStatus.open) -> List["Issue"]:
"""
List of issues (dics)
Expand Down
72 changes: 70 additions & 2 deletions ogr/services/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# SOFTWARE.

import logging
from typing import Optional, Dict, List, Type
from typing import Optional, Dict, List, Type, Set

import github
from github import (
Expand Down Expand Up @@ -229,6 +229,74 @@ def get_fork(self, create: bool = True) -> Optional["GithubProject"]:
return None
return self._construct_fork_project()

def get_owners(self) -> List[str]:
# in case of github, repository has only one owner
return [self.github_repo.owner.login]

def who_can_close_issue(self) -> Set[str]:
try:
collaborators = self._get_collaborators_with_permission()
except github.GithubException:
logger.debug(
f"Current Github token must have push access to view repository permissions."
)
return set()

usernames = []
for login, permission in collaborators.items():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I would like to see also the list/set of allowed users. (Since you are going through all of the collaborators and we are not saving any time with that.)

We have following possibilities:

  1. who_can_close_issues and can_close_issue (which will use who_can_close_issues and checks the author of the issue) (We can move the can_close_issue to the Issue object later.)
  2. who_can_close_issue with the optional parameter for specifying the issue id
  3. leave it in the current state

I vote for the second one but make your own decision...;-)

(Same for the pull-requests.)

if permission in ["admin", "write"]:
usernames.append(login)

return set(usernames)

def who_can_merge_pr(self) -> Set[str]:
try:
collaborators = self._get_collaborators_with_permission()
except github.GithubException:
logger.debug(
f"Current Github token must have push access to view repository permissions."
)
return set()

usernames = []
for login, permission in collaborators.items():
if permission in ["admin", "write"]:
usernames.append(login)

return set(usernames)

def can_close_issue(self, username: str, issue: Issue) -> bool:
allowed_users = self.who_can_close_issue()

for allowed_user in allowed_users:
if username == allowed_user:
return True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this:

if username in allowed_users:
    return True

if username == issue.author:
return True

return False

def can_merge_pr(self, username) -> bool:
allowed_users = self.who_can_merge_pr()

for allowed_user in allowed_users:
if username == allowed_user:
return True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.


return False

def _get_collaborators_with_permission(self) -> dict:
"""
Get all project collaborators in dictionary with permission association
:return: List of usernames
"""
collaborators = {}
users = self.github_repo.get_collaborators()
for user in users:
permission = self.github_repo.get_collaborator_permission(user)
collaborators[user.login] = permission
return collaborators

def get_issue_list(self, status: IssueStatus = IssueStatus.open) -> List[Issue]:
issues = self.github_repo.get_issues(
state=status.name, sort="updated", direction="desc"
Expand Down Expand Up @@ -472,7 +540,7 @@ def _issue_from_github_object(github_issue: GithubIssue) -> Issue:
status=IssueStatus[github_issue.state],
url=github_issue.html_url,
description=github_issue.body,
author=github_issue.user.name,
author=github_issue.user.login,
created=github_issue.created_at,
)

Expand Down
39 changes: 38 additions & 1 deletion ogr/services/pagure.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

import datetime
import logging
from typing import List, Optional, Dict, Any
from typing import List, Optional, Dict, Any, Set

import requests

Expand Down Expand Up @@ -357,6 +357,43 @@ def get_branches(self) -> List[str]:
def get_description(self) -> str:
return self.get_project_info()["description"]

def get_owners(self) -> List[str]:
project = self.get_project_info()
return project["access_users"]["owner"]

def who_can_close_issue(self) -> Set[str]:
users: Set[str] = set()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about returning set for all methods (github/pagure/abstract)?

project = self.get_project_info()
users.update(project["access_users"]["admin"])
users.update(project["access_users"]["commit"])
users.update(project["access_users"]["ticket"])
users.update(project["access_users"]["owner"])
return users

def who_can_merge_pr(self) -> Set[str]:
users: Set[str] = set()
project = self.get_project_info()
users.update(project["access_users"]["admin"])
users.update(project["access_users"]["commit"])
users.update(project["access_users"]["owner"])
return users

def can_close_issue(self, username: str, issue: Issue) -> bool:
allowed_users = self.who_can_close_issue()
if username in allowed_users:
return True
if username == issue.author:
return True

return False

def can_merge_pr(self, username) -> bool:
allowed_users = self.who_can_merge_pr()
if username in allowed_users:
return True

return False

def get_issue_list(self, status: IssueStatus = IssueStatus.open) -> List[Issue]:
payload = {"status": status.name.capitalize()}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
GET:
/repos/user-cont/colin:
empty:
- - 200
- access-control-allow-origin: '*'
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP,
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes,
X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type
cache-control: private, max-age=60, s-maxage=60
content-encoding: gzip
content-security-policy: default-src 'none'
content-type: application/json; charset=utf-8
date: Mon, 08 Jul 2019 12:10:27 GMT
etag: W/"e3a9b05a4f4afef0c8b28a04a22e0545"
last-modified: Tue, 02 Jul 2019 13:34:17 GMT
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
server: GitHub.com
status: 200 OK
strict-transport-security: max-age=31536000; includeSubdomains; preload
transfer-encoding: chunked
vary: Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding
x-accepted-oauth-scopes: repo
x-content-type-options: nosniff
x-frame-options: deny
x-github-media-type: github.v3; format=json
x-github-request-id: CDA6:35215:5774FAF:6CE91C0:5D2332B2
x-oauth-scopes: delete_repo, repo
x-ratelimit-limit: '5000'
x-ratelimit-remaining: '4998'
x-ratelimit-reset: '1562591426'
x-xss-protection: 1; mode=block
- '{"id":124359252,"node_id":"MDEwOlJlcG9zaXRvcnkxMjQzNTkyNTI=","name":"colin","full_name":"user-cont/colin","private":false,"owner":{"login":"user-cont","id":35456931,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM1NDU2OTMx","avatar_url":"https://avatars1.githubusercontent.com/u/35456931?v=4","gravatar_id":"","url":"https://api.github.com/users/user-cont","html_url":"https://github.com/user-cont","followers_url":"https://api.github.com/users/user-cont/followers","following_url":"https://api.github.com/users/user-cont/following{/other_user}","gists_url":"https://api.github.com/users/user-cont/gists{/gist_id}","starred_url":"https://api.github.com/users/user-cont/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/user-cont/subscriptions","organizations_url":"https://api.github.com/users/user-cont/orgs","repos_url":"https://api.github.com/users/user-cont/repos","events_url":"https://api.github.com/users/user-cont/events{/privacy}","received_events_url":"https://api.github.com/users/user-cont/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/user-cont/colin","description":"Tool
to check generic rules/best-practices for containers/images/dockerfiles.","fork":false,"url":"https://api.github.com/repos/user-cont/colin","forks_url":"https://api.github.com/repos/user-cont/colin/forks","keys_url":"https://api.github.com/repos/user-cont/colin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/user-cont/colin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/user-cont/colin/teams","hooks_url":"https://api.github.com/repos/user-cont/colin/hooks","issue_events_url":"https://api.github.com/repos/user-cont/colin/issues/events{/number}","events_url":"https://api.github.com/repos/user-cont/colin/events","assignees_url":"https://api.github.com/repos/user-cont/colin/assignees{/user}","branches_url":"https://api.github.com/repos/user-cont/colin/branches{/branch}","tags_url":"https://api.github.com/repos/user-cont/colin/tags","blobs_url":"https://api.github.com/repos/user-cont/colin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/user-cont/colin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/user-cont/colin/git/refs{/sha}","trees_url":"https://api.github.com/repos/user-cont/colin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/user-cont/colin/statuses/{sha}","languages_url":"https://api.github.com/repos/user-cont/colin/languages","stargazers_url":"https://api.github.com/repos/user-cont/colin/stargazers","contributors_url":"https://api.github.com/repos/user-cont/colin/contributors","subscribers_url":"https://api.github.com/repos/user-cont/colin/subscribers","subscription_url":"https://api.github.com/repos/user-cont/colin/subscription","commits_url":"https://api.github.com/repos/user-cont/colin/commits{/sha}","git_commits_url":"https://api.github.com/repos/user-cont/colin/git/commits{/sha}","comments_url":"https://api.github.com/repos/user-cont/colin/comments{/number}","issue_comment_url":"https://api.github.com/repos/user-cont/colin/issues/comments{/number}","contents_url":"https://api.github.com/repos/user-cont/colin/contents/{+path}","compare_url":"https://api.github.com/repos/user-cont/colin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/user-cont/colin/merges","archive_url":"https://api.github.com/repos/user-cont/colin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/user-cont/colin/downloads","issues_url":"https://api.github.com/repos/user-cont/colin/issues{/number}","pulls_url":"https://api.github.com/repos/user-cont/colin/pulls{/number}","milestones_url":"https://api.github.com/repos/user-cont/colin/milestones{/number}","notifications_url":"https://api.github.com/repos/user-cont/colin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/user-cont/colin/labels{/name}","releases_url":"https://api.github.com/repos/user-cont/colin/releases{/id}","deployments_url":"https://api.github.com/repos/user-cont/colin/deployments","created_at":"2018-03-08T08:15:35Z","updated_at":"2019-07-02T13:34:17Z","pushed_at":"2019-05-24T08:20:01Z","git_url":"git://github.com/user-cont/colin.git","ssh_url":"[email protected]:user-cont/colin.git","clone_url":"https://github.com/user-cont/colin.git","svn_url":"https://github.com/user-cont/colin","homepage":null,"size":6494,"stargazers_count":37,"watchers_count":37,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":15,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":23,"license":{"key":"gpl-3.0","name":"GNU
General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":15,"open_issues":23,"watchers":37,"default_branch":"master","permissions":{"admin":false,"push":true,"pull":true},"allow_squash_merge":true,"allow_merge_commit":true,"allow_rebase_merge":true,"organization":{"login":"user-cont","id":35456931,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM1NDU2OTMx","avatar_url":"https://avatars1.githubusercontent.com/u/35456931?v=4","gravatar_id":"","url":"https://api.github.com/users/user-cont","html_url":"https://github.com/user-cont","followers_url":"https://api.github.com/users/user-cont/followers","following_url":"https://api.github.com/users/user-cont/following{/other_user}","gists_url":"https://api.github.com/users/user-cont/gists{/gist_id}","starred_url":"https://api.github.com/users/user-cont/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/user-cont/subscriptions","organizations_url":"https://api.github.com/users/user-cont/orgs","repos_url":"https://api.github.com/users/user-cont/repos","events_url":"https://api.github.com/users/user-cont/events{/privacy}","received_events_url":"https://api.github.com/users/user-cont/received_events","type":"Organization","site_admin":false},"network_count":15,"subscribers_count":10}'
/user:
empty:
- - 200
- access-control-allow-origin: '*'
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP,
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes,
X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type
cache-control: private, max-age=60, s-maxage=60
content-encoding: gzip
content-security-policy: default-src 'none'
content-type: application/json; charset=utf-8
date: Mon, 08 Jul 2019 12:10:26 GMT
etag: W/"c847a70dc190f0a03e0e3370fcbc10aa"
last-modified: Mon, 08 Jul 2019 11:45:31 GMT
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
server: GitHub.com
status: 200 OK
strict-transport-security: max-age=31536000; includeSubdomains; preload
transfer-encoding: chunked
vary: Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding
x-accepted-oauth-scopes: ''
x-content-type-options: nosniff
x-frame-options: deny
x-github-media-type: github.v3; format=json
x-github-request-id: CDA6:35215:5774F7F:6CE9185:5D2332B2
x-oauth-scopes: delete_repo, repo
x-ratelimit-limit: '5000'
x-ratelimit-remaining: '4999'
x-ratelimit-reset: '1562591426'
x-xss-protection: 1; mode=block
- "{\"login\":\"rpitonak\",\"id\":26160778,\"node_id\":\"MDQ6VXNlcjI2MTYwNzc4\"\
,\"avatar_url\":\"https://avatars3.githubusercontent.com/u/26160778?v=4\"\
,\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/rpitonak\",\"\
html_url\":\"https://github.com/rpitonak\",\"followers_url\":\"https://api.github.com/users/rpitonak/followers\"\
,\"following_url\":\"https://api.github.com/users/rpitonak/following{/other_user}\"\
,\"gists_url\":\"https://api.github.com/users/rpitonak/gists{/gist_id}\",\"\
starred_url\":\"https://api.github.com/users/rpitonak/starred{/owner}{/repo}\"\
,\"subscriptions_url\":\"https://api.github.com/users/rpitonak/subscriptions\"\
,\"organizations_url\":\"https://api.github.com/users/rpitonak/orgs\",\"repos_url\"\
:\"https://api.github.com/users/rpitonak/repos\",\"events_url\":\"https://api.github.com/users/rpitonak/events{/privacy}\"\
,\"received_events_url\":\"https://api.github.com/users/rpitonak/received_events\"\
,\"type\":\"User\",\"site_admin\":false,\"name\":\"Radoslav Pito\u0148\xE1\
k\",\"company\":null,\"blog\":\"\",\"location\":\"Brno\",\"email\":\"[email protected]\"\
,\"hireable\":null,\"bio\":null,\"public_repos\":32,\"public_gists\":2,\"\
followers\":11,\"following\":14,\"created_at\":\"2017-03-03T08:33:44Z\",\"\
updated_at\":\"2019-07-08T11:45:31Z\"}"
Loading