Skip to content

Commit

Permalink
Merge pull request #313 from arnaudh/ah/more-info-when-unauthorized
Browse files Browse the repository at this point in the history
WIP Display reason when user is unauthorized
  • Loading branch information
nkottary authored Jan 5, 2021
2 parents 4852041 + f434034 commit 840bb46
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 19 deletions.
28 changes: 25 additions & 3 deletions Manifest.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# This file is machine-generated - editing it directly is not advised

[[Artifacts]]
deps = ["Pkg"]
git-tree-sha1 = "c30985d8821e0cd73870b17b0ed0ce6dc44cb744"
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
version = "1.3.0"

[[AssetRegistry]]
deps = ["Distributed", "JSON", "Pidfile", "SHA", "Test"]
git-tree-sha1 = "b25e88db7944f98789130d7b503276bc34bc098e"
Expand Down Expand Up @@ -32,6 +38,11 @@ uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[ExprTools]]
git-tree-sha1 = "10407a39b87f29d47ebaca8edbc75d7c302ff93e"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
version = "0.1.3"

[[FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"

Expand Down Expand Up @@ -74,6 +85,11 @@ git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLLWrappers]]
git-tree-sha1 = "c70593677bbf2c3ccab4f7500d0f4dacfff7b75c"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.1.3"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "b34d7cef7b337321e97d22242c3c2b91f476748e"
Expand Down Expand Up @@ -131,6 +147,12 @@ version = "2.16.6+1"
[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[Mocking]]
deps = ["ExprTools"]
git-tree-sha1 = "916b850daad0d46b8c71f65f719c49957e9513ed"
uuid = "78c3b35d-d492-501b-9361-3d52fe80e533"
version = "0.7.1"

[[Mustache]]
deps = ["Printf", "Tables"]
git-tree-sha1 = "fcfc8266461f2905534aa00c0fc59b8751b1026e"
Expand Down Expand Up @@ -228,7 +250,7 @@ uuid = "c2297ded-f4af-51ae-bb23-16f91089e4e1"
version = "1.2.1"

[[ZeroMQ_jll]]
deps = ["Libdl", "Pkg"]
git-tree-sha1 = "733352667c60ce39dfd3017db9b798b288c87417"
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "bba617292e040408cb72baa03c20f43583bf239f"
uuid = "8f1865be-045e-5c20-9c9f-bfbfb0764568"
version = "4.3.2+4"
version = "4.3.2+5"
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
MbedTLS = "739be429-bea8-5141-9913-cc70e7f3736d"
Mocking = "78c3b35d-d492-501b-9361-3d52fe80e533"
Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70"
Mux = "a975b10e-0019-58db-a62f-e48ff68538c9"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Expand Down
58 changes: 43 additions & 15 deletions src/webui/gitutils.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ..Registrator: decodeb64
using Mocking

# # Run some GitForge function, warning on error but still returning the value.
macro gf(ex::Expr)
Expand All @@ -25,13 +26,23 @@ getrepo(::GitLabAPI, owner::AbstractString, name::AbstractString) =
getrepo(::GitHubAPI, owner::AbstractString, name::AbstractString) =
@gf get_repo(PROVIDERS["github"].client, owner, name)


abstract type AuthResult end
struct AuthSuccess <: AuthResult end
struct AuthFailure <: AuthResult
reason::AbstractString
end

is_success(res::AuthSuccess) = true
is_success(res::AuthFailure) = false

# Check for a user's authorization to release a package.
# The criteria is simply whether the user is a collaborator for user-owned repos,
# or whether they're an organization member or collaborator for organization-owned repos.
isauthorized(u, repo) = false
isauthorized(u, repo) = AuthFailure("Unkown user type or repo type")
function isauthorized(u::User{GitHub.User}, repo::GitHub.Repo)
if !get(CONFIG, "allow_private", false)
repo.private && return false
repo.private && return AuthFailure("Repo $(repo.name) is private")
end

if repo.private
Expand All @@ -40,20 +51,29 @@ function isauthorized(u::User{GitHub.User}, repo::GitHub.Repo)
forge = u.forge
end

hasauth = if repo.organization === nothing
@gf is_collaborator(forge, repo.owner.login, repo.name, u.user.login)
if repo.organization === nothing
hasauth = @gf @mock is_collaborator(forge, repo.owner.login, repo.name, u.user.login)
if something(hasauth, false)
return AuthSuccess()
else
return AuthFailure("User $(u.user.login) is not a collaborator on repo $(repo.name)")
end
else
# First check for organization membership, and fall back to collaborator status.
ismember = @gf is_member(forge, repo.organization.login, u.user.login)
something(ismember, false) ||
@gf is_collaborator(forge, repo.organization.login, repo.name, u.user.login)
ismember = @gf @mock is_member(forge, repo.organization.login, u.user.login)
hasauth = something(ismember, false) ||
@gf @mock is_collaborator(forge, repo.organization.login, repo.name, u.user.login)
if something(hasauth, false)
return AuthSuccess()
else
return AuthFailure("User $(u.user.login) is not a member of the org $(repo.organization.login) and not a collaborator on repo $(repo.name)")
end
end
return something(hasauth, false)
end

function isauthorized(u::User{GitLab.User}, repo::GitLab.Project)
if !get(CONFIG, "allow_private", false)
repo.visibility == "private" && return false
repo.visibility == "private" && return AuthFailure("Project $(repo.name) is private")
end

if repo.visibility == "private"
Expand All @@ -62,23 +82,31 @@ function isauthorized(u::User{GitLab.User}, repo::GitLab.Project)
forge = u.forge
end

hasauth = if repo.namespace.kind == "user"
@gf is_collaborator(forge, repo.owner.username, repo.name, u.user.id)
if repo.namespace.kind == "user"
hasauth = @gf @mock is_collaborator(forge, repo.owner.username, repo.name, u.user.id)
if something(hasauth, false)
return AuthSuccess()
else
return AuthFailure("User $(u.user.name) is not a member of project $(repo.name)") # GitLab terminology "member" (not "collaborator")
end
else
# Same as above: group membership then collaborator check.
nspath = split(repo.namespace.full_path, "/")
ismember = @gf is_collaborator(u.forge, repo.namespace.full_path, repo.name, u.user.id)
ismember = @gf @mock is_collaborator(u.forge, repo.namespace.full_path, repo.name, u.user.id)
if !something(ismember, false)
accns = ""
for ns in nspath
accns = joinpath(accns, ns)
ismember = @gf is_member(forge, accns, u.user.id)
ismember = @gf @mock is_member(forge, accns, u.user.id)
something(ismember, false) && break
end
end
ismember
if ismember
return AuthSuccess()
else
return AuthFailure("Project $(repo.name) belongs to the group $(repo.namespace.full_path), and user $(u.user.name) is not a member of that group or its parent group(s)")
end
end
return something(hasauth, false)
end

# Get the raw (Julia)Project.toml text from a repository.
Expand Down
5 changes: 4 additions & 1 deletion src/webui/routes/register.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ function register(r::HTTP.Request)
owner, name = splitrepo(package)
repo = getrepo(u.forge, owner, name)
repo === nothing && return json(400; error="Repository was not found")
isauthorized(u, repo) || return json(400; error="Unauthorized to release this package")
auth_result = isauthorized(u, repo)
if !is_success(auth_result)
return json(400; error="Unauthorized to release this package. Reason: $(auth_result.reason)")
end

# Get the (Julia)Project.toml, and make sure it is valid.
toml = gettoml(u.forge, repo, ref)
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ if get(ENV, "TRAVIS", "") == "true" && !haskey(ENV, "GITHUB_API_TOKEN")
else
include("webui.jl")
end
include("webui/gitutils.jl")

end
111 changes: 111 additions & 0 deletions test/webui/gitutils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using Dates: DateTime
using Registrator.WebUI: isauthorized, AuthFailure, AuthSuccess, User
using GitForge: GitForge, GitHub, GitLab
using HTTP: stacktrace

using Mocking

Mocking.activate()

function patch_gitforge(body::Function; is_collaborator=false, is_member=false)
patches = [
@patch GitForge.is_collaborator(args...) =
GitForge.Result{Bool}(is_collaborator, nothing, nothing, stacktrace())
@patch GitForge.is_member(args...) =
GitForge.Result{Bool}(is_member, nothing, nothing, stacktrace())
]

apply(patches) do
return body()
end
end

@testset "gitutils" begin

@testset "isauthorized" begin
@test isauthorized("username", "reponame") == AuthFailure("Unkown user type or repo type")

@testset "GitHub" begin

user = GitHub.User(login="user123")
org = GitHub.User(login="JuliaLang")
private_repo = GitHub.Repo(name="Example.jl", private=true, owner=user)
public_repo_of_user = GitHub.Repo(name="Example.jl", private=false, owner=user, organization=nothing)
public_repo_of_org = GitHub.Repo(name="Example.jl", private=false, owner=org, organization=org)
u = User(user, GitHub.GitHubAPI())

@testset "private repo" begin
# Assuming CONFIG["allow_private"] is false
@test isauthorized(u, private_repo) == AuthFailure("Repo Example.jl is private")
end

@testset "public repo of user" begin
# authorized if user is a collaborator on the repo
patch_gitforge(is_collaborator=true) do
@test isauthorized(u, public_repo_of_user) == AuthSuccess()
end
patch_gitforge(is_collaborator=false) do
@test isauthorized(u, public_repo_of_user) == AuthFailure("User user123 is not a collaborator on repo Example.jl")
end
end

@testset "public repo of org" begin
# authorized if user is either a collaborator on the repo or member of the org
patch_gitforge(is_collaborator=true, is_member=true) do
@test isauthorized(u, public_repo_of_org) == AuthSuccess()
end
patch_gitforge(is_collaborator=true, is_member=false) do
@test isauthorized(u, public_repo_of_org) == AuthSuccess()
end
patch_gitforge(is_collaborator=false, is_member=true) do
@test isauthorized(u, public_repo_of_org) == AuthSuccess()
end
patch_gitforge(is_collaborator=false, is_member=false) do
@test isauthorized(u, public_repo_of_org) == AuthFailure("User user123 is not a member of the org JuliaLang and not a collaborator on repo Example.jl")
end
end
end

@testset "GitLab" begin

user = GitLab.User(name="user123", username="user123", id=111)
org = GitLab.User(name="org123", username="org123", id=222)
private_project = GitLab.Project(name="Example.jl", visibility="private", owner=user)
public_project_of_user = GitLab.Project(name="Example.jl", visibility="public", owner=user, namespace=GitLab.Namespace(kind="user"))
public_project_of_group = GitLab.Project(name="Example.jl", visibility="public", owner=org, namespace=GitLab.Namespace(kind="group", full_path="org123/subgroup/Example.jl"))
u = User(user, GitLab.GitLabAPI())

@testset "private project" begin
# Assuming CONFIG["allow_private"] is false
@test isauthorized(u, private_project) == AuthFailure("Project Example.jl is private")
end

@testset "public project of user" begin
# authorized if user is a collaborator on the project
patch_gitforge(is_collaborator=true) do
@test isauthorized(u, public_project_of_user) == AuthSuccess()
end
patch_gitforge(is_collaborator=false) do
@test isauthorized(u, public_project_of_user) == AuthFailure("User user123 is not a member of project Example.jl")
end
end

@testset "public project of group" begin
# authorized if user is a collaborator on the project or member of the group/subgroups
patch_gitforge(is_collaborator=true, is_member=true) do
@test isauthorized(u, public_project_of_group) == AuthSuccess()
end
patch_gitforge(is_collaborator=false, is_member=true) do
@test isauthorized(u, public_project_of_group) == AuthSuccess()
end
patch_gitforge(is_collaborator=true, is_member=false) do
@test isauthorized(u, public_project_of_group) == AuthSuccess()
end
patch_gitforge(is_collaborator=false, is_member=false) do
@test isauthorized(u, public_project_of_group) == AuthFailure("Project Example.jl belongs to the group org123/subgroup/Example.jl, and user user123 is not a member of that group or its parent group(s)")
end
end
end
end

end

0 comments on commit 840bb46

Please sign in to comment.