Skip to content

Commit

Permalink
Merge pull request #85 from dwyl/rbac-issue-31
Browse files Browse the repository at this point in the history
PR: RBAC Role Based Access Control #27 #31 #82
  • Loading branch information
th0mas authored Sep 17, 2020
2 parents fafc5e0 + 23df807 commit 5de6261
Show file tree
Hide file tree
Showing 79 changed files with 3,553 additions and 745 deletions.
6 changes: 4 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
language: elixir
elixir:
- 1.10.2
- 1.10.4
otp_release:
- 22.1.8
- 23.0.3
services:
- postgresql
env:
- MIX_ENV=test
before_script:
# create .env file on Travis-CI:
- echo "export MIX_ENV=test" > .env
- mix ecto.setup
script:
- mix do deps.get, coveralls.json
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ you can setup in ***5 minutes***.
<!-- uncomment when service is working ...
[![Inline docs](http://inch-ci.org/github/dwyl/auth.svg?branch=master&style=flat-square)](http://inch-ci.org/github/dwyl/auth)
-->
![wake-sleeping-heroku-app](https://dwylauth.herokuapp.com/ping)

</div>

Expand Down Expand Up @@ -173,7 +174,6 @@ And for sending emails you will need the
`SECRET_KEY_BASE` and `EMAIL_APP_URL` defined.



### 4. Create and migrate your database:

> Ensure that PostgreSQL is running
Expand All @@ -190,7 +190,7 @@ mix ecto.setup
mix phoenix.server
```

> It may take a couple of minutes to compile the app the first time. ⏳
> It may take a minute to compile the app the first time. ⏳
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.

Expand Down
4 changes: 2 additions & 2 deletions elixir_buildpack.config
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
109 changes: 59 additions & 50 deletions lib/auth/apikey.ex
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
Expand All @@ -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)
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

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"))
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)
end

@doc """
Updates a apikey.
Expand All @@ -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
155 changes: 155 additions & 0 deletions lib/auth/app.ex
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)
|> 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)
|> 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
3 changes: 3 additions & 0 deletions lib/auth/login_log.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
defmodule Auth.LoginLog do
@moduledoc """
Defines login_logs schema and CRUD functions
"""
use Ecto.Schema
import Ecto.Changeset
alias Auth.Repo
Expand Down
Loading

0 comments on commit 5de6261

Please sign in to comment.