-
Notifications
You must be signed in to change notification settings - Fork 182
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
Add (REST) API for managing (GraphQL) API credentials(/tokens) #436
Conversation
5b7450d
to
4e54d7e
Compare
lib/ret/api/token_utils.ex
Outdated
defp ensure_atom(x) when is_binary(x), do: String.to_atom(x) | ||
|
||
# TODO Should we permit the creation of tokens | ||
# on behalf of other accounts like this? |
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.
It feels odd to allow this, but it seemed like an easy way for us to do a closed alpha/beta test of the API on HMC. Alternatively, we could add a column (e.g. an integer bitmask) to the database and use it to assign roles, then check these roles to see if the given account has permission to create credentials.
lib/ret/hub.ex
Outdated
scopes: scopes, | ||
account_id: account_id | ||
}) do | ||
# TODO: Is it correct to disallow the create_accounts scope for account type tokens? |
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.
I got it into my head that only :app
credentials would be allowed to have the create_accounts
scope, but then I don't have a good reason for this. Maybe only admin accounts should be allowed to create :account
credentials that have the create_accounts
scope. Then the only important distinction between :app
credentials and :account
credentials is the fact that :app
credentials are accessible / managed / shared by all admin accounts.
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.
I was a bit confused by this since I thought we removed create_accounts
from #399 since it was not implemented. But I see create_accounts
still exists in Ret.Api.Scaopes
. I would suggest we remove create_accounts
from these PRs altogether and also, importantly, ensure that input validation rejects claims that don't exist before it even gets to this point.
end | ||
|
||
def can?(%Ret.Account{}, :list_credentials, :account) do | ||
# TODO: Allow admins to disable this in config |
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.
I don't know how/where to add config values. I'll have to learn
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.
Happy to help if needed. See examples of where we use "features|public_rooms"
and get_config_bool
for reference. We might want to introduce a cached version, get_cached_config_bool
if we're concerned about database hits for config values, and if a stale cache delay is acceptable.
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.
Added in f52a5c2 and Hubs-Foundation/hubs#3551
only: [to_claims: 2, authed_create_credentials: 2, authed_list_credentials: 2, authed_revoke_credentials: 2] | ||
|
||
# Limit to 1 TPS | ||
# TODO : I copied this from other controllers. Is this an appropriate limit? -John |
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.
Perhaps the same limit should be added to the graphql endpoint
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.
Definitely seems appropriate for this API. Not sure about the entire graphql endpoint.
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.
Looks good, though we should definitely resolve our approach to this in general, as we discussed.
lib/ret/api/credentials.ex
Outdated
@@ -87,11 +87,11 @@ defmodule Ret.Api.Credentials do | |||
end | |||
end | |||
|
|||
defp validate_subject_type(:subject_type, subject_type) do | |||
def validate_subject_type(:subject_type, subject_type) do |
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.
nit: Might be better to abstract these a bit, instead of exposing the callback used by validate_change
. It's a bit awkward to have the callers repeat :scopes
and :subject_type
. In fact, it may be better to create a private validate_field
function that overloads for each field type instead, and use that for validate_change
. Something like:
defp validate_field(:scopes, scopes) do
...
end
defp validate_field(:subject_type, scopes) do
...
end
def validate_scopes(scopes) do
validate_field(:scopes, scopes)
end
def validate_subject_type(subject_type) do
validate_field(:subject_type, subject_type)
end
end
lib/ret/api/token_utils.ex
Outdated
defp validate_account_id(%Account{}, account_id) do | ||
case Account.query() | ||
|> Account.where_account_id_is(account_id) | ||
|> Repo.one() do |
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.
I'd suggest using Repo.exists?
instead of actually getting the account object.
lib/ret/api/token_utils.ex
Outdated
with [] <- | ||
validate_account_id(account, account_id) ++ | ||
Credentials.validate_scopes_type(:scopes, scopes) ++ | ||
Credentials.validate_subject_type(:subject_type, subject_type) do |
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.
nit: Not sure if we've been using this pattern already, but it does feel a bit too clever to use with
in this way, since we're not actually doing anything with the matched value and implicitly allowing with
to return the error list. Seems like this is one of those times where it's better to be clear than concise.
lib/ret/hub.ex
Outdated
scopes: scopes, | ||
account_id: account_id | ||
}) do | ||
# TODO: Is it correct to disallow the create_accounts scope for account type tokens? |
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.
I was a bit confused by this since I thought we removed create_accounts
from #399 since it was not implemented. But I see create_accounts
still exists in Ret.Api.Scaopes
. I would suggest we remove create_accounts
from these PRs altogether and also, importantly, ensure that input validation rejects claims that don't exist before it even gets to this point.
end | ||
|
||
def can?(%Ret.Account{}, :list_credentials, :account) do | ||
# TODO: Allow admins to disable this in config |
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.
Happy to help if needed. See examples of where we use "features|public_rooms"
and get_config_bool
for reference. We might want to introduce a cached version, get_cached_config_bool
if we're concerned about database hits for config values, and if a stale cache delay is acceptable.
case Credentials.query() | ||
|> Credentials.where_sid_is(credentials_sid) | ||
|> Repo.one() do |
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.
I'd be inclined to use Repo.get_by()
instead of constructing a query.
end | ||
|
||
def render("show.json", %{credentials: credentials}) do | ||
render("show.json", %{credentials: credentials, token: nil}) |
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.
Bit strange that token
is on the outer result here, but inside credentials:
in the other views.
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.
I may be misunderstanding your comment - but token
always ends up as a key inside the credentials
json, not outside it. This line is just redirecting to the other render("show.json", ...)
(which does the call to render_credentials
and merges in the token
).
Open to suggestions to make this clearer -- My intention is to return a token
in the credentials
object when you first create one, but then have token: null
every time after that.
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.
Ah, my mistake, I misread the code paths in this case.
lib/ret/hub.ex
Outdated
# TODO: Is it correct to disallow the create_accounts scope for account type tokens? | ||
# If so, why? Do we need to enforce this everywhere? | ||
Scopes.create_accounts() not in scopes | ||
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.
Maybe we should only allow admins to create credentials for the first release, at least until we decide on doing roles?
|> get(list) | ||
|> json_response(200) | ||
|> Map.get("credentials") | ||
|> Enum.count() === 0 |
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.
We do this enough in the tests that it should be a get_credentials_count
helper.
|> hd() | ||
|> Map.get("is_revoked") | ||
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.
👍 nice tests!
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.
Thanks :)
I have not wrapped my head around or studied up on best practices for testing elixir + phoenix apps. Here I test an interface (CredentialsController
) and the model (Credentials
) together, which I could have done this separately. I am happy enough with it as it is, but want to be better about this in the future. This seems like it'll be increasingly important as we have alternative interfaces, transports, and views (REST over HTTP, graphql, websockets passing json).
Allow admin accounts to create creds on behalf of other accounts
1ea3272
to
d3adae7
Compare
No description provided.