-
Notifications
You must be signed in to change notification settings - Fork 9
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
PR: RBAC Role Based Access Control #27 #31 #82 #85
Changes from all commits
0d4dfd5
a351bfc
c2f3d19
090aa66
46a2813
46c45c7
2f26ed1
0114fc8
18be2a0
e961a17
a202d73
39ff5c1
f468b4d
2a3c361
2b7a66d
b6e4a5f
0498e98
9cf0ef5
eab0291
f03c050
b60522a
1762d43
5ec8568
8f192b1
7377027
ef4261d
723ae83
7a8d967
3c1c720
510489c
c1f9d3b
b27a21a
259e239
506d365
1d6a259
812e192
5a0d8bf
3ce9bab
198b3fb
d3fa7de
9dfc9de
aed44b8
89acf44
f0a7782
3218d5c
361648d
d27444b
b652d34
0f45966
7310d60
ffca1fa
f4aeb67
0b1848d
7f2286a
d6ccfa5
b9c2cd8
7ac38b3
73a6107
7335bbd
a22dd7b
4568c7d
3df1c93
2eacfeb
46e4a8a
7abbd6f
5389ea4
f90ef84
01b2b87
52c0828
3100d28
fdea23d
45b7dbe
ecc6043
a4d5e58
b7188d0
87c52d6
5d52ed3
22f5d50
2aa97f7
205c828
2da212d
0ae675b
e07952e
36fd51e
978504f
78cb2d8
b3e5576
8e6c096
dd97914
35515a6
c5d7594
341c1e1
05f88d7
88d31c2
df79ca2
b1f7c4a
c17bcbb
bc136ce
13cca0e
259149d
b0ff09f
f59cdf9
e6b478a
3859c9f
82f8ff1
3c4e5a2
3190d72
f45a850
8ec7669
5a21c74
cb45d38
bebb939
930ad6e
b56b840
ec389ab
59560c0
063e362
64c88e8
93e7292
d8b7e0f
e5d8c5e
a199c9e
5b8d32e
f09a184
568c770
5c12cf6
607f775
f261c85
fdab8cf
e9c63cd
943ad03
2b87797
1de4a08
231d10a
ef2fa11
cbd51a5
c9de4a5
f7cb9a1
6cd4495
297b93a
5ca7ebc
ca5fcfa
5242c6b
7849d5e
8395345
131efde
d57d17a
6c94745
80d3f6e
f27ce54
818bba2
333a448
707f4af
83ea6cb
c5f1821
035022b
2aa91e5
650635e
e608d27
304a53f
9fdcf70
966f748
71f6d0f
ecae729
8738b44
be9b1f9
23df807
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
# Elixir version | ||
elixir_version=1.10 | ||
elixir_version=1.10.4 | ||
|
||
# Erlang version | ||
# available versions https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions | ||
erlang_version=22.2.7 | ||
erlang_version=23.0.3 | ||
|
||
# always_rebuild=true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
defmodule Auth.Apikey do | ||
@moduledoc """ | ||
Defines apikeys schema and CRUD functions | ||
""" | ||
use Ecto.Schema | ||
import Ecto.Query, warn: false | ||
import Ecto.Changeset | ||
|
@@ -9,57 +12,79 @@ defmodule Auth.Apikey do | |
schema "apikeys" do | ||
field :client_secret, :binary | ||
field :client_id, :binary | ||
field :description, :string | ||
field :name, :string | ||
field :url, :binary | ||
field :person_id, :id | ||
field :status, :id | ||
belongs_to :app, Auth.App | ||
|
||
timestamps() | ||
end | ||
|
||
@doc false | ||
def changeset(apikey, attrs) do | ||
apikey | ||
|> cast(attrs, [:client_id, :client_secret, :name, :description, :url, :person_id]) | ||
|> validate_required([:client_secret]) | ||
@doc """ | ||
`encrypt_encode/1` does exactly what it's name suggests, | ||
AES Encrypts a string of plaintext and then Base58 encodes it. | ||
We encode it using Base58 so it's human-friendly (readable). | ||
""" | ||
def encrypt_encode(plaintext) do | ||
plaintext |> Fields.AES.encrypt() |> Base58.encode() | ||
end | ||
|
||
def change_apikey(%Apikey{} = apikey) do | ||
Apikey.changeset(apikey, %{}) | ||
@doc """ | ||
`create_api_key/1` uses the `encrypt_encode/1` to create an API Key | ||
that is just two strings joined with a forwardslash ("/"). | ||
This allows us to use a *single* environment variable. | ||
""" | ||
def create_api_key(id) do | ||
encrypt_encode(id) <> "/" <> encrypt_encode(id) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this just duplicate the string? - am i missing something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Close. On first inspection this appears to be the case, but actually these two strings are always different. |
||
end | ||
|
||
def create_apikey(attrs \\ %{}) do | ||
%Apikey{} | ||
|> Apikey.changeset(attrs) | ||
|> Repo.insert() | ||
@doc """ | ||
`decode_decrypt/1` accepts a `key` and attempts to Base58.decode | ||
followed by AES.decrypt it. If decode or decrypt fails, return 0 (zero). | ||
""" | ||
def decode_decrypt(key) do | ||
try do | ||
key |> Base58.decode() |> Fields.AES.decrypt() |> String.to_integer() | ||
rescue | ||
ArgumentError -> | ||
0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason for returning There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I honestly don't mind refactoring this. I just thought the |
||
|
||
ArithmeticError -> | ||
0 | ||
end | ||
end | ||
|
||
def list_apikeys_for_person(person_id) do | ||
query = | ||
from( | ||
a in __MODULE__, | ||
where: a.person_id == ^person_id | ||
) | ||
|
||
Repo.all(query) | ||
def decrypt_api_key(key) do | ||
key |> String.split("/") |> List.first() |> decode_decrypt() | ||
end | ||
|
||
@doc """ | ||
Gets a single apikey. | ||
|
||
Raises `Ecto.NoResultsError` if the Apikey does not exist. | ||
|
||
## Examples | ||
def changeset(apikey, attrs) do | ||
apikey | ||
|> cast(attrs, [:client_id, :client_secret, :status, :person_id]) | ||
|> put_assoc(:app, Map.get(attrs, "app")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this might allow anyone to associate an API key with any "app", unless theres validation to check that the user owns the app beforehand. It might be worth refactoring There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @th0mas I briefly thought of removing I'd actually be quite happy to re-build this whole thing from scratch in order to simplify it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still think this is a potential security risk as anytime any api is key is created or changed, there is the possibility of injecting another app as its owner. |
||
end | ||
|
||
iex> get_apikey!(123) | ||
%Apikey{} | ||
def create_apikey(app) do | ||
attrs = %{ | ||
"client_secret" => encrypt_encode(app.id), | ||
"client_id" => encrypt_encode(app.id), | ||
"person_id" => app.person_id, | ||
"status" => 3, | ||
"app" => app | ||
} | ||
|
||
iex> get_apikey!(456) | ||
** (Ecto.NoResultsError) | ||
%Apikey{} | ||
|> Apikey.changeset(attrs) | ||
|> Repo.insert() | ||
end | ||
|
||
""" | ||
def get_apikey!(id), do: Repo.get!(__MODULE__, id) | ||
def get_apikey_by_app_id(app_id) do | ||
from( | ||
a in __MODULE__, | ||
where: a.app_id == ^app_id | ||
) | ||
|> Repo.one() | ||
|> Repo.preload(:app) | ||
nelsonic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
@doc """ | ||
Updates a apikey. | ||
|
@@ -78,20 +103,4 @@ defmodule Auth.Apikey do | |
|> changeset(attrs) | ||
|> Repo.update() | ||
end | ||
|
||
@doc """ | ||
Deletes a apikey. | ||
|
||
## Examples | ||
|
||
iex> delete_apikey(apikey) | ||
{:ok, %Apikey{}} | ||
|
||
iex> delete_apikey(apikey) | ||
{:error, %Ecto.Changeset{}} | ||
|
||
""" | ||
def delete_apikey(%Apikey{} = apikey) do | ||
Repo.delete(apikey) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,155 @@ | ||||||
defmodule Auth.App do | ||||||
@moduledoc """ | ||||||
Schema and helper functions for creating/managing Apps. | ||||||
""" | ||||||
use Ecto.Schema | ||||||
import Ecto.Changeset | ||||||
import Ecto.Query, warn: false | ||||||
alias Auth.Repo | ||||||
# https://stackoverflow.com/a/47501059/1148249 | ||||||
alias __MODULE__ | ||||||
|
||||||
schema "apps" do | ||||||
field :desc, :binary | ||||||
field :end, :naive_datetime | ||||||
field :name, :binary | ||||||
field :url, :binary | ||||||
field :person_id, :id | ||||||
field :status, :id | ||||||
has_many :apikeys, Auth.Apikey | ||||||
|
||||||
timestamps() | ||||||
end | ||||||
|
||||||
@doc false | ||||||
def changeset(app, attrs) do | ||||||
app | ||||||
|> cast(attrs, [:name, :desc, :url, :end, :person_id, :status]) | ||||||
|> validate_required([:name, :url]) | ||||||
end | ||||||
|
||||||
@doc """ | ||||||
Returns the list of apps. | ||||||
|
||||||
## Examples | ||||||
|
||||||
iex> list_apps() | ||||||
[%App{}, ...] | ||||||
|
||||||
""" | ||||||
def list_apps do | ||||||
Repo.all(App) | ||||||
end | ||||||
|
||||||
# Returning all apps when person_id == 1 (superadmin) means | ||||||
# the superadmin can always see/manage all apps as necessary. | ||||||
# Later we could refactor this function to use RBAC.has_role_any/2. | ||||||
def list_apps(conn) when is_map(conn) do | ||||||
case conn.assigns.person.id == 1 do | ||||||
true -> Auth.App.list_apps() | ||||||
false -> Auth.App.list_apps(conn.assigns.person.id) | ||||||
end | ||||||
end | ||||||
|
||||||
def list_apps(person_id) do | ||||||
App | ||||||
|> where([a], a.status != 6 and a.person_id == ^person_id) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a magic number here is still confusing, these should be refactored into constants There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. This can be improved I was just hard-coding it because (at least for now) these are known values. Lines 28 to 29 in f27ce54
|
||||||
|> Repo.all() | ||||||
end | ||||||
|
||||||
@doc """ | ||||||
Gets a single app. | ||||||
|
||||||
Raises `Ecto.NoResultsError` if the App does not exist. | ||||||
|
||||||
## Examples | ||||||
|
||||||
iex> get_app!(123) | ||||||
%App{} | ||||||
|
||||||
iex> get_app!(456) | ||||||
** (Ecto.NoResultsError) | ||||||
|
||||||
""" | ||||||
def get_app!(id) do | ||||||
App | ||||||
|> where([a], a.id == ^id and a.status != 6) | ||||||
nelsonic marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|> Repo.one() | ||||||
|> Repo.preload(:apikeys) | ||||||
end | ||||||
|
||||||
@doc """ | ||||||
Creates a app. | ||||||
|
||||||
## Examples | ||||||
|
||||||
iex> create_app(%{field: value}) | ||||||
{:ok, %App{}} | ||||||
|
||||||
iex> create_app(%{field: bad_value}) | ||||||
{:error, %Ecto.Changeset{}} | ||||||
|
||||||
""" | ||||||
def create_app(attrs \\ %{}) do | ||||||
case %App{} |> App.changeset(attrs) |> Repo.insert() do | ||||||
{:ok, app} -> | ||||||
# Create API Key for App https://github.com/dwyl/auth/issues/97 | ||||||
Auth.Apikey.create_apikey(app) | ||||||
|
||||||
# return the App with the API Key preloaded: | ||||||
{:ok, get_app!(app.id)} | ||||||
|
||||||
{:error, err} -> | ||||||
{:error, err} | ||||||
end | ||||||
end | ||||||
|
||||||
@doc """ | ||||||
Updates a app. | ||||||
|
||||||
## Examples | ||||||
|
||||||
iex> update_app(app, %{field: new_value}) | ||||||
{:ok, %App{}} | ||||||
|
||||||
iex> update_app(app, %{field: bad_value}) | ||||||
{:error, %Ecto.Changeset{}} | ||||||
|
||||||
""" | ||||||
def update_app(%App{} = app, attrs) do | ||||||
app | ||||||
# |> IO.inspect(label: "update_app/2:109") | ||||||
|> App.changeset(attrs) | ||||||
|> Repo.update() | ||||||
end | ||||||
|
||||||
@doc """ | ||||||
Deletes a app. | ||||||
|
||||||
## Examples | ||||||
|
||||||
iex> delete_app(app) | ||||||
{:ok, %App{}} | ||||||
|
||||||
iex> delete_app(app) | ||||||
{:error, %Ecto.Changeset{}} | ||||||
|
||||||
""" | ||||||
def delete_app(%App{} = app) do | ||||||
# "soft delete" for autiting purposes: | ||||||
update_app(app, %{status: 6}) | ||||||
end | ||||||
|
||||||
@doc """ | ||||||
Returns an `%Ecto.Changeset{}` for tracking app changes. | ||||||
|
||||||
## Examples | ||||||
|
||||||
iex> change_app(app) | ||||||
%Ecto.Changeset{data: %App{}} | ||||||
|
||||||
""" | ||||||
def change_app(%App{} = app, attrs \\ %{}) do | ||||||
App.changeset(app, attrs) | ||||||
end | ||||||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These functions aren't new. I just moved them over from the
ApikeysController
file which was deleted in this PR. 👍