Skip to content

Commit 30ea1fc

Browse files
committed
Adding user authentication functionality and including authorization in CI pipeline
1 parent 99b8eb8 commit 30ea1fc

File tree

9 files changed

+156
-5
lines changed

9 files changed

+156
-5
lines changed

.github/workflows/CI.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- name: Setup mlflow locally
3535
run: |
3636
pip install -r ./requirements.txt
37-
python3 /opt/hostedtoolcache/Python/3.10.13/x64/bin/mlflow server --host 0.0.0.0 --port 5000 &
37+
python3 /opt/hostedtoolcache/Python/3.10.13/x64/bin/mlflow server --app-name basic-auth --host 0.0.0.0 --port 5000 &
3838
sleep 5
3939
- uses: julia-actions/setup-julia@v1
4040
with:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ mlruns
77
coverage
88
Pipfile
99
Pipfile.lock
10+
*.db

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ URIs = "1.0"
1919
julia = "1.0"
2020

2121
[extras]
22+
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
2223
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2324

2425
[targets]
25-
test = ["Test"]
26+
test = ["Base64", "Test"]

src/MLFlowClient.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,7 @@ export getlatestmodelversions, getmodelversion, createmodelversion, deletemodelv
8181
transitionmodelversionstage, setmodelversiontag, deletemodelversiontag,
8282
getmodelversionbyalias
8383

84+
include("services/user.jl")
85+
export createuser, getuser, updateuserpassword, updateuseradmin, deleteuser
86+
8487
end

src/services/user.jl

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""
2+
createuser(instance::MLFlow, username::String, password::String)
3+
4+
# Arguments
5+
- `instance`: [`MLFlow`](@ref) configuration.
6+
- `username`: Username.
7+
- `password`: Password.
8+
9+
# Returns
10+
An [`User`](@ref) object.
11+
"""
12+
function createuser(instance::MLFlow, username::String, password::String)::User
13+
result = mlfpost(instance, "users/create"; username=username, password=password)
14+
return result["user"] |> User
15+
end
16+
17+
"""
18+
getuser(instance::MLFlow, username::String)
19+
20+
# Arguments
21+
- `instance`: [`MLFlow`](@ref) configuration.
22+
- `username`: Username.
23+
24+
# Returns
25+
An [`User`](@ref) object.
26+
"""
27+
function getuser(instance::MLFlow, username::String)::User
28+
result = mlfget(instance, "users/get"; username=username)
29+
return result["user"] |> User
30+
end
31+
32+
"""
33+
deleteuser(instance::MLFlow, username::String, password::String)
34+
35+
# Arguments
36+
- `instance`: [`MLFlow`](@ref) configuration.
37+
- `username`: Username.
38+
- `password`: New password.
39+
40+
# Returns
41+
`true` if successful. Otherwise, raises exception.
42+
"""
43+
function updateuserpassword(instance::MLFlow, username::String, password::String)::Bool
44+
mlfpatch(instance, "users/update-password"; username=username, password=password)
45+
return true
46+
end
47+
48+
"""
49+
updateuseradmin(instance::MLFlow, username::String, is_admin::Bool)
50+
51+
# Arguments
52+
- `instance`: [`MLFlow`](@ref) configuration.
53+
- `username`: Username.
54+
- `is_admin`: New admin status.
55+
56+
# Returns
57+
`true` if successful. Otherwise, raises exception.
58+
"""
59+
function updateuseradmin(instance::MLFlow, username::String, is_admin::Bool)::Bool
60+
mlfpatch(instance, "users/update-admin"; username=username, is_admin=is_admin)
61+
return true
62+
end
63+
64+
"""
65+
deleteuser(instance::MLFlow, username::String)
66+
67+
# Arguments
68+
- `instance`: [`MLFlow`](@ref) configuration.
69+
- `username`: Username.
70+
71+
# Returns
72+
`true` if successful. Otherwise, raises exception.
73+
"""
74+
function deleteuser(instance::MLFlow, username::String)::Bool
75+
mlfdelete(instance, "users/delete"; username=username)
76+
return true
77+
end

src/types/user.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ struct User
1717
experiment_permissions::Array{ExperimentPermission}
1818
registered_model_permissions::Array{RegisteredModelPermission}
1919
end
20+
User(data::Dict{String,Any}) = User(data["id"] |> string, data["username"], data["is_admin"],
21+
[ExperimentPermission(permission) for permission in get(data, "experiment_permissions", [])],
22+
[RegisteredModelPermission(permission) for permission in get(data, "registered_model_permissions", [])])
23+
Base.show(io::IO, t::User) = show(io, ShowCase(t, new_lines=true))

test/base.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
using MLFlowClient
21
using Test
3-
using UUIDs
42
using Dates
3+
using UUIDs
4+
using Base64
5+
using MLFlowClient
56

67
function mlflow_server_is_running(mlf::MLFlow)
78
try
@@ -16,7 +17,8 @@ end
1617
# skips test if mlflow is not available on default location, ENV["MLFLOW_TRACKING_URI"]
1718
macro ensuremlf()
1819
e = quote
19-
mlf = MLFlow()
20+
encoded_credentials = Base64.base64encode("admin:password")
21+
mlf = MLFlow(headers=Dict("Authorization" => "Basic $(encoded_credentials)"))
2022
mlflow_server_is_running(mlf) || return nothing
2123
end
2224
eval(e)

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ include("services/artifact.jl")
1111
include("services/experiment.jl")
1212
include("services/registered_model.jl")
1313
include("services/model_version.jl")
14+
include("services/user.jl")

test/services/user.jl

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
@testset verbose = true "create user" begin
2+
@ensuremlf
3+
4+
user = createuser(mlf, "missy", "gala")
5+
6+
@test user isa User
7+
@test user.username == "missy"
8+
@test user.is_admin == false
9+
10+
deleteuser(mlf, user.username)
11+
end
12+
13+
@testset verbose = true "get user" begin
14+
@ensuremlf
15+
16+
user = createuser(mlf, "missy", "gala")
17+
18+
retrieved_user = getuser(mlf, "missy")
19+
20+
@test retrieved_user isa User
21+
@test retrieved_user.username == "missy"
22+
@test retrieved_user.is_admin == false
23+
24+
deleteuser(mlf, retrieved_user.username)
25+
end
26+
27+
@testset verbose = true "update user password" begin
28+
@ensuremlf
29+
30+
getmlfinstance(encoded_credentials::String) =
31+
MLFlow(headers=Dict("Authorization" => "Basic $(encoded_credentials)"))
32+
33+
user = createuser(mlf, "missy", "gala")
34+
encoded_credentials = Base64.base64encode("$(user.username):gala")
35+
36+
updateuserpassword(getmlfinstance(encoded_credentials), "missy", "ana")
37+
encoded_credentials = Base64.base64encode("$(user.username):ana")
38+
39+
@test_nowarn searchexperiments(getmlfinstance(encoded_credentials))
40+
deleteuser(mlf, user.username)
41+
end
42+
43+
@testset verbose = true "update user admin" begin
44+
@ensuremlf
45+
46+
user = createuser(mlf, "missy", "gala")
47+
updateuseradmin(mlf, "missy", true)
48+
49+
retrieved_user = getuser(mlf, "missy")
50+
@test retrieved_user.is_admin == true
51+
52+
deleteuser(mlf, retrieved_user.username)
53+
end
54+
55+
@testset verbose = true "delete user" begin
56+
@ensuremlf
57+
58+
user = createuser(mlf, "missy", "gala")
59+
deleteuser(mlf, "missy")
60+
61+
@test_throws ErrorException getuser(mlf, "missy")
62+
end

0 commit comments

Comments
 (0)