Skip to content

Commit 0d6d12c

Browse files
committed
init git
0 parents  commit 0d6d12c

9 files changed

Lines changed: 353 additions & 0 deletions

File tree

.github/workflows/CompatHelper.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: CompatHelper
2+
on:
3+
schedule:
4+
- cron: 0 0 * * *
5+
workflow_dispatch:
6+
jobs:
7+
CompatHelper:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Pkg.add("CompatHelper")
11+
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
12+
- name: CompatHelper.main()
13+
env:
14+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15+
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
16+
run: julia -e 'using CompatHelper; CompatHelper.main()'

.github/workflows/TagBot.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: TagBot
2+
on:
3+
issue_comment:
4+
types:
5+
- created
6+
workflow_dispatch:
7+
jobs:
8+
TagBot:
9+
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: JuliaRegistries/TagBot@v1
13+
with:
14+
token: ${{ secrets.GITHUB_TOKEN }}
15+
ssh: ${{ secrets.DOCUMENTER_KEY }}

.github/workflows/ci.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: ci
2+
on:
3+
- push
4+
- pull_request
5+
jobs:
6+
test:
7+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
8+
runs-on: ${{ matrix.os }}
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
version:
13+
- '1.6'
14+
- '1'
15+
- 'nightly'
16+
os:
17+
- ubuntu-latest
18+
- macOS-latest
19+
- windows-latest
20+
arch:
21+
- x64
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: julia-actions/setup-julia@latest
25+
with:
26+
version: ${{ matrix.version }}
27+
arch: ${{ matrix.arch }}
28+
- uses: julia-actions/julia-buildpkg@latest
29+
- uses: julia-actions/julia-runtest@latest
30+
build:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@v4
34+
- uses: julia-actions/setup-julia@latest
35+
with:
36+
version: '1.7'
37+
- name: Install dependencies
38+
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
39+
# - name: Build and deploy
40+
# env:
41+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token
42+
# run: julia --project=docs/ docs/make.jl

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/dev/
3+
.tags
4+
.DS_Store
5+
.vscode
6+
.theme
7+
test/build
8+
Manifest.toml

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Genie Framework
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Project.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name = "StippleUsers"
2+
uuid = "fc25ca13-2340-47d7-8ff6-e6adb00fe34b"
3+
version = "0.1.0"
4+
5+
[deps]
6+
Stipple = "4acbeb90-81a0-11ea-1966-bdaff8155998"
7+
8+
[compat]
9+
Stipple = "0.28.14 - 0.31"
10+
Test = "1.6"
11+
julia = "1.6"
12+
13+
[extras]
14+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
15+
16+
[targets]
17+
test = ["Test"]

README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# StippleUsers
2+
3+
A middle layer to provide consistent user authorisation independent of the methods of authentication and authorisation.
4+
5+
The package exports:
6+
7+
- `default_user()` - the default username, if only a single user is required (mainly used to keep the app functional without any specialisation)
8+
- `default_role()` - the default role that is attributed to each user, default: `user`
9+
- `get_user()` - obtain the current user, needs to be specialised (can be any type), default: `default_user()`
10+
- `get_roles()` - obtain the roles of the current user, needs to be specialised, default: `user !== nothing ? [default_role(App)] : String[]`
11+
- `is_authorised(; role = default_role())` - return whether the current user has the role or any of the roles defined by the `role` keyword, can be specialised
12+
13+
Customization is typically done by specialising `get_user()` and `get_roles()`, it is also possible to specialise `is_authorised()` but not recommended.
14+
As the middle layer is designed to allow for app-dependent authorisation, specialisation needs provide the app type it is meant to work with, e.g.
15+
16+
#### with GenieAuthentication
17+
```julia
18+
using GenieAuthentication
19+
StippleUsers.get_user(::AllApps) = get_authentication()
20+
```
21+
to provide the user id of GenieAuthentication for all apps. Note that `const AllApps = Typ{<:ReactiveModel}` has been defined for convenience.
22+
#### with Snowpark Container Services
23+
```julia
24+
StippleUsers.get_user(::Type{SnowApp}) = get(headers(), "sf-context-current-user", "not_authorised")
25+
StippleUsers.get_roles(::Type{SnowApp}, user::String) = user == "not_authorised" ? String[] : [default_role()]
26+
```
27+
to provide the Snowpark user for the (explicit) app `SnowApp`
28+
29+
### Example 1 - Numbered Users and Admins for a all Apps
30+
```julia
31+
using Stipple, Stipple.ReactiveTools, StippleUsers
32+
33+
@app MyApp begin
34+
@in x = 1
35+
end
36+
37+
# define roles as the first part of the string
38+
StippleUsers.get_roles(::AllApps, user::String) = String[split(user, '_')[1]]
39+
40+
# case 1: normal user
41+
StippleUsers.get_user(::AllApps) = "user_1"
42+
43+
get_roles()
44+
# ["user"]
45+
46+
is_authorised()
47+
# true
48+
49+
is_authorised(role = "admin")
50+
# false
51+
52+
# case 2: Admin
53+
StippleUsers.get_user(::AllApps) = "admin_1"
54+
55+
get_roles()
56+
# ["admin"]
57+
58+
is_authorised()
59+
# false
60+
61+
is_authorised(role = "admin")
62+
# true
63+
```
64+
65+
### Example 2 - User List via Roles for a single App
66+
```julia
67+
using Stipple, Stipple.ReactiveTools, StippleUsers
68+
69+
@app MyApp begin
70+
@in x = 1
71+
end
72+
73+
# make roles identical to user
74+
StippleUsers.get_roles(::Type{MyApp}, user::String) = [user]
75+
76+
# case 1: normal user
77+
StippleUsers.get_user(::Type{MyApp}) = "user_1"
78+
79+
get_roles(MyApp)
80+
# ["user_1"]
81+
82+
is_authorised(MyApp)
83+
# false
84+
85+
userlist() = ["user_1", "admin_1"]
86+
is_authorised(MyApp, role = userlist())
87+
# true
88+
89+
# case 2: Admin
90+
StippleUsers.get_user(::Type{MyApp}) = "admin_1"
91+
92+
get_roles(MyApp)
93+
# ["admin_1"]
94+
95+
is_authorised(MyApp, role = userlist())
96+
# true
97+
```
98+
99+
### Example 3 - Overwriting `is_authorised()` for a specific implicit model
100+
```julia
101+
using Stipple, Stipple.ReactiveTools, StippleUsers
102+
103+
# define an implicit model
104+
@app begin
105+
@in x = 1
106+
end
107+
108+
userlist() = ["user_1", "admin_1"]
109+
110+
# define `App` as the module's implicit model
111+
App = Stipple.@type
112+
113+
# define `is_authorised()` for the implicit App
114+
# Note that it is important to allow for kwargs, because internal handling will call `is_authorised()` with the `role` kwarg.
115+
StippleUsers.is_authorised(::Type{App}, user::String; kwargs...) = user userlist()
116+
117+
StippleUsers.get_user(::Type{App}) = "user_1"
118+
is_authorised(App)
119+
# true
120+
121+
StippleUsers.get_user(::Type{App}) = "user_2"
122+
is_authorised(App)
123+
# false
124+
125+
StippleUsers.get_user(::Type{App}) = "admin_1"
126+
is_authorised(App)
127+
# true
128+
```
129+
130+
### Example 4 - applying authorisation to a Genie web application
131+
132+
```julia
133+
using Stipple, Stipple.ReactiveTools, StippleUsers
134+
135+
@app MyApp begin
136+
@in x = 1
137+
end
138+
139+
# create a random user or admin
140+
StippleUsers.get_user(::Type{MyApp}) = "$(rand(("user", "admin")))_$(rand(1:100))"
141+
142+
# define the role as first part of the string
143+
StippleUsers.get_roles(::Type{MyApp}, user::String) = String[split(user, '_')[1]]
144+
145+
# define an admin check which returns nothing in case of success and a not-found-page in case of failure
146+
admin_check() = is_authorised(MyApp, role = "admin") ? nothing : not_found()
147+
148+
ui() = "Hello Admin!"
149+
@page("/", ui, model = MyApp, pre = admin_check)
150+
151+
up(open_browser = true)
152+
# reload the page several times; depending on whether you are a (random) admin, the page will be shown or not
153+
```

src/StippleUsers.jl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module StippleUsers
2+
3+
using Stipple
4+
5+
export default_user, default_role, get_user, get_roles, is_authorised, not_found, AllApps
6+
7+
not_found() = throw(Genie.Exceptions.NotFoundException(Genie.Requests.matchedroute().path))
8+
const AllApps = Type{<:ReactiveModel}
9+
10+
# functions to be specialised
11+
12+
default_user(::Type) = "default_user"
13+
default_role(::Type)::String = "user"
14+
15+
get_user(::Type{App}) where App = default_user(App)
16+
17+
function get_roles(::Type{App}, user) where App
18+
user !== nothing ? [default_role(App)] : String[]
19+
end
20+
21+
function is_authorised(::Type{App}, user; role::Union{String, Vector{String}} = default_role(App)) where App
22+
roles = get_roles(App, user)
23+
if role isa String
24+
role roles
25+
else
26+
!isempty(intersect(role, roles))
27+
end
28+
end
29+
30+
# ----------------------------------------------------------------
31+
32+
default_user() = default_user(ReactiveModel)
33+
default_role() = default_role(ReactiveModel)
34+
get_user() = get_user(ReactiveModel)
35+
36+
get_roles(::Type{App}) where App <: ReactiveModel = get_roles(App, get_user(App))
37+
get_roles(user = get_user(ReactiveModel)) = get_roles(ReactiveModel, user)
38+
39+
function is_authorised(::Type{App}; role::Union{String, Vector{String}} = "user") where App <: ReactiveModel
40+
is_authorised(App, get_user(App); role)
41+
end
42+
43+
function is_authorised(user = get_user(ReactiveModel); role::Union{String, Vector{String}} = "user")
44+
is_authorised(ReactiveModel, user; role)
45+
end
46+
47+
end # module

test/runtests.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Test
2+
using Stipple, Stipple.ReactiveTools
3+
using StippleUsers
4+
5+
@app MyApp begin
6+
@in x = 1
7+
end
8+
9+
10+
@test default_user() == "default_user"
11+
@test get_roles() == ["user"]
12+
13+
StippleUsers.get_roles(::AllApps, user::String) = String[split(user, '_')[1]]
14+
15+
StippleUsers.get_user(::AllApps) = "user_1"
16+
17+
@test get_roles() == ["user"]
18+
19+
@test is_authorised()
20+
@test !is_authorised(role = "admin")
21+
@test is_authorised(role = ["user", "admin"])
22+
23+
StippleUsers.get_user(::AllApps) = "admin_1"
24+
25+
@test get_roles() == ["admin"]
26+
@test !is_authorised()
27+
@test is_authorised(role = "admin")
28+
@test is_authorised(role = ["user", "admin"])
29+
30+
StippleUsers.get_user(::Type{MyApp}) = "myuser_1"
31+
@test !is_authorised()
32+
@test !is_authorised(role = "myuser")
33+
@test !is_authorised(MyApp)
34+
@test is_authorised(MyApp, role = "myuser")

0 commit comments

Comments
 (0)