FEATURE: user API now contains scopes so permission is granular
previously we supported blanket read and write for user API, this change amends it so we can define more limited scopes. A scope only covers a few routes. You can not grant access to part of the site and leave a large amount of the information hidden to API consumer.
This commit is contained in:
parent
becff2de4d
commit
f4f5524190
|
@ -336,7 +336,13 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
{{d-button action="revokeApiKey" actionParam=key class="btn" label="user.revoke_access"}}
|
{{d-button action="revokeApiKey" actionParam=key class="btn" label="user.revoke_access"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<p><span>{{i18n "user.api_permissions"}}</span> {{#if key.write}}{{i18n "user.api_read_write"}}{{else}}{{i18n "user.api_read"}}{{/if}}</p>
|
<p>
|
||||||
|
<ul>
|
||||||
|
{{#each key.scopes as |scope|}}
|
||||||
|
<li>{{scope}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
<p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p>
|
<p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
@ -6,7 +6,7 @@ class UserApiKeysController < ApplicationController
|
||||||
skip_before_filter :check_xhr, :preload_json
|
skip_before_filter :check_xhr, :preload_json
|
||||||
before_filter :ensure_logged_in, only: [:create, :revoke, :undo_revoke]
|
before_filter :ensure_logged_in, only: [:create, :revoke, :undo_revoke]
|
||||||
|
|
||||||
AUTH_API_VERSION ||= 1
|
AUTH_API_VERSION ||= 2
|
||||||
|
|
||||||
def new
|
def new
|
||||||
|
|
||||||
|
@ -34,14 +34,14 @@ class UserApiKeysController < ApplicationController
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@access_description = params[:access].include?("w") ? t("user_api_key.read_write") : t("user_api_key.read")
|
|
||||||
@application_name = params[:application_name]
|
@application_name = params[:application_name]
|
||||||
@public_key = params[:public_key]
|
@public_key = params[:public_key]
|
||||||
@nonce = params[:nonce]
|
@nonce = params[:nonce]
|
||||||
@access = params[:access]
|
|
||||||
@client_id = params[:client_id]
|
@client_id = params[:client_id]
|
||||||
@auth_redirect = params[:auth_redirect]
|
@auth_redirect = params[:auth_redirect]
|
||||||
@push_url = params[:push_url]
|
@push_url = params[:push_url]
|
||||||
|
@localized_scopes = params[:scopes].split(",").map{|s| I18n.t("user_api_key.scopes.#{s}")}
|
||||||
|
@scopes = params[:scopes]
|
||||||
|
|
||||||
rescue Discourse::InvalidAccess
|
rescue Discourse::InvalidAccess
|
||||||
@generic_error = true
|
@generic_error = true
|
||||||
|
@ -60,10 +60,6 @@ class UserApiKeysController < ApplicationController
|
||||||
|
|
||||||
raise Discourse::InvalidAccess unless meets_tl?
|
raise Discourse::InvalidAccess unless meets_tl?
|
||||||
|
|
||||||
request_read = params[:access].include? 'r'
|
|
||||||
request_read ||= params[:access].include? 'p'
|
|
||||||
request_write = params[:access].include? 'w'
|
|
||||||
|
|
||||||
validate_params
|
validate_params
|
||||||
|
|
||||||
# destroy any old keys we had
|
# destroy any old keys we had
|
||||||
|
@ -72,12 +68,10 @@ class UserApiKeysController < ApplicationController
|
||||||
key = UserApiKey.create!(
|
key = UserApiKey.create!(
|
||||||
application_name: params[:application_name],
|
application_name: params[:application_name],
|
||||||
client_id: params[:client_id],
|
client_id: params[:client_id],
|
||||||
read: request_read,
|
|
||||||
push: params[:push_url].present?,
|
|
||||||
user_id: current_user.id,
|
user_id: current_user.id,
|
||||||
write: request_write,
|
push_url: params[:push_url],
|
||||||
key: SecureRandom.hex,
|
key: SecureRandom.hex,
|
||||||
push_url: params[:push_url]
|
scopes: params[:scopes].split(",")
|
||||||
)
|
)
|
||||||
|
|
||||||
# we keep the payload short so it encrypts easily with public key
|
# we keep the payload short so it encrypts easily with public key
|
||||||
|
@ -85,7 +79,8 @@ class UserApiKeysController < ApplicationController
|
||||||
payload = {
|
payload = {
|
||||||
key: key.key,
|
key: key.key,
|
||||||
nonce: params[:nonce],
|
nonce: params[:nonce],
|
||||||
access: key.access
|
push: key.has_push?,
|
||||||
|
api: AUTH_API_VERSION
|
||||||
}.to_json
|
}.to_json
|
||||||
|
|
||||||
public_key = OpenSSL::PKey::RSA.new(params[:public_key])
|
public_key = OpenSSL::PKey::RSA.new(params[:public_key])
|
||||||
|
@ -100,7 +95,7 @@ class UserApiKeysController < ApplicationController
|
||||||
if current_key = request.env['HTTP_USER_API_KEY']
|
if current_key = request.env['HTTP_USER_API_KEY']
|
||||||
request_key = UserApiKey.find_by(key: current_key)
|
request_key = UserApiKey.find_by(key: current_key)
|
||||||
revoke_key ||= request_key
|
revoke_key ||= request_key
|
||||||
if request_key && request_key.id != revoke_key.id && !request_key.write
|
if request_key && request_key.id != revoke_key.id && !request_key.scopes.include?("write")
|
||||||
raise Discourse::InvalidAccess
|
raise Discourse::InvalidAccess
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -127,7 +122,7 @@ class UserApiKeysController < ApplicationController
|
||||||
[
|
[
|
||||||
:public_key,
|
:public_key,
|
||||||
:nonce,
|
:nonce,
|
||||||
:access,
|
:scopes,
|
||||||
:client_id,
|
:client_id,
|
||||||
:auth_redirect,
|
:auth_redirect,
|
||||||
:application_name
|
:application_name
|
||||||
|
@ -135,13 +130,9 @@ class UserApiKeysController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_params
|
def validate_params
|
||||||
request_read = params[:access].include? 'r'
|
requested_scopes = Set.new(params[:scopes].split(","))
|
||||||
request_read ||= params[:access].include? 'p'
|
|
||||||
request_write = params[:access].include? 'w'
|
|
||||||
|
|
||||||
raise Discourse::InvalidAccess unless request_read || request_push
|
raise Discourse::InvalidAccess unless UserApiKey.allowed_scopes.superset?(requested_scopes)
|
||||||
raise Discourse::InvalidAccess if request_read && !SiteSetting.allow_read_user_api_keys
|
|
||||||
raise Discourse::InvalidAccess if request_write && !SiteSetting.allow_write_user_api_keys
|
|
||||||
|
|
||||||
# our pk has got to parse
|
# our pk has got to parse
|
||||||
OpenSSL::PKey::RSA.new(params[:public_key])
|
OpenSSL::PKey::RSA.new(params[:public_key])
|
||||||
|
|
|
@ -1,10 +1,63 @@
|
||||||
class UserApiKey < ActiveRecord::Base
|
class UserApiKey < ActiveRecord::Base
|
||||||
|
|
||||||
|
SCOPES = {
|
||||||
|
read: [:get],
|
||||||
|
write: [:get, :post, :patch],
|
||||||
|
message_bus: [[:post, 'message_bus']],
|
||||||
|
push: nil,
|
||||||
|
notifications: [[:post, 'message_bus'], [:get, 'notifications#index'], [:put, 'notifications#mark_read']],
|
||||||
|
session_info: [[:get, 'session#current'], [:get, 'users#topic_tracking_state']]
|
||||||
|
}
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
def access
|
def self.allowed_scopes
|
||||||
has_push = push && push_url.present? && SiteSetting.allowed_user_api_push_urls.include?(push_url)
|
Set.new(SiteSetting.allow_user_api_key_scopes.split("|"))
|
||||||
"#{read ? "r" : ""}#{write ? "w" : ""}#{has_push ? "p" : ""}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.available_scopes
|
||||||
|
@available_scopes ||= Set.new(SCOPES.keys.map(&:to_s))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.allow_permission?(permission, env)
|
||||||
|
verb, action = permission
|
||||||
|
actual_verb = env["REQUEST_METHOD"] || ""
|
||||||
|
|
||||||
|
# safe in Ruby 2.3 which is only one supported
|
||||||
|
return false unless actual_verb.downcase == verb.to_s
|
||||||
|
return true unless action
|
||||||
|
|
||||||
|
# not a rails route, special handling
|
||||||
|
return true if action == "message_bus" && env["PATH_INFO"] =~ /^\/message-bus\/.*\/poll/
|
||||||
|
|
||||||
|
params = env['action_dispatch.request.path_parameters']
|
||||||
|
|
||||||
|
return false unless params
|
||||||
|
|
||||||
|
actual_action = "#{params[:controller]}##{params[:action]}"
|
||||||
|
actual_action == action
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.allow_scope?(name, env)
|
||||||
|
if allowed = SCOPES[name.to_sym]
|
||||||
|
good = allowed.any? do |permission|
|
||||||
|
allow_permission?(permission, env)
|
||||||
|
end
|
||||||
|
|
||||||
|
good || allow_permission?([:post, 'user_api_keys#revoke'], env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_push?
|
||||||
|
(scopes.include?("push") || scopes.include?("notifications")) && push_url.present? && SiteSetting.allowed_user_api_push_urls.include?(push_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def allow?(env)
|
||||||
|
scopes.any? do |name|
|
||||||
|
UserApiKey.allow_scope?(name, env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
|
|
|
@ -148,8 +148,7 @@ class UserSerializer < BasicUserSerializer
|
||||||
{
|
{
|
||||||
id: k.id,
|
id: k.id,
|
||||||
application_name: k.application_name,
|
application_name: k.application_name,
|
||||||
read: k.read,
|
scopes: k.scopes.map{|s| I18n.t("user_api_key.scopes.#{s}")},
|
||||||
write: k.write,
|
|
||||||
created_at: k.created_at
|
created_at: k.created_at
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -396,9 +396,9 @@ class PostAlerter
|
||||||
end
|
end
|
||||||
|
|
||||||
def push_notification(user, payload)
|
def push_notification(user, payload)
|
||||||
if SiteSetting.allow_push_user_api_keys && SiteSetting.allowed_user_api_push_urls.present?
|
if SiteSetting.allow_user_api_key_scopes.split("|").include?("push") && SiteSetting.allowed_user_api_push_urls.present?
|
||||||
clients = user.user_api_keys
|
clients = user.user_api_keys
|
||||||
.where('push AND push_url IS NOT NULL AND position(push_url in ?) > 0 AND revoked_at IS NULL',
|
.where("'push' = ANY(scopes) AND push_url IS NOT NULL AND position(push_url in ?) > 0 AND revoked_at IS NULL",
|
||||||
SiteSetting.allowed_user_api_push_urls)
|
SiteSetting.allowed_user_api_push_urls)
|
||||||
.pluck(:client_id, :push_url)
|
.pluck(:client_id, :push_url)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<h1><%= t "user_api_key.title" %></h1>
|
<h1><%= t "user_api_key.title" %></h1>
|
||||||
<div>
|
<div class='authorize-api-key'>
|
||||||
<% if @no_trust_level %>
|
<% if @no_trust_level %>
|
||||||
<h3>
|
<h3>
|
||||||
<%= t("user_api_key.no_trust_level") %>
|
<%= t("user_api_key.no_trust_level") %>
|
||||||
|
@ -10,7 +10,14 @@
|
||||||
</h3>
|
</h3>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>
|
<p>
|
||||||
<%= t("user_api_key.description", application_name: @application_name, access: @access_description) %>
|
<%= t("user_api_key.description", application_name: @application_name) %>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<ul class='scopes'>
|
||||||
|
<%- @localized_scopes.each do |scope| %>
|
||||||
|
<li><%= scope %></li>
|
||||||
|
<%- end %>
|
||||||
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<%= form_tag(user_api_key_path) do %>
|
<%= form_tag(user_api_key_path) do %>
|
||||||
<%= hidden_field_tag 'application_name', @application_name %>
|
<%= hidden_field_tag 'application_name', @application_name %>
|
||||||
|
@ -20,6 +27,7 @@
|
||||||
<%= hidden_field_tag 'auth_redirect', @auth_redirect %>
|
<%= hidden_field_tag 'auth_redirect', @auth_redirect %>
|
||||||
<%= hidden_field_tag 'push_url', @push_url %>
|
<%= hidden_field_tag 'push_url', @push_url %>
|
||||||
<%= hidden_field_tag 'public_key', @public_key%>
|
<%= hidden_field_tag 'public_key', @public_key%>
|
||||||
|
<%= hidden_field_tag 'scopes', @scopes%>
|
||||||
<%= submit_tag t('user_api_key.authorize'), class: 'btn btn-danger', id: 'submit' %>
|
<%= submit_tag t('user_api_key.authorize'), class: 'btn btn-danger', id: 'submit' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -570,10 +570,7 @@ en:
|
||||||
apps: "Apps"
|
apps: "Apps"
|
||||||
revoke_access: "Revoke Access"
|
revoke_access: "Revoke Access"
|
||||||
undo_revoke_access: "Undo Revoke Access"
|
undo_revoke_access: "Undo Revoke Access"
|
||||||
api_permissions: "Permissions:"
|
|
||||||
api_approved: "Approved:"
|
api_approved: "Approved:"
|
||||||
api_read: "read"
|
|
||||||
api_read_write: "read and write"
|
|
||||||
|
|
||||||
staff_counters:
|
staff_counters:
|
||||||
flags_given: "helpful flags"
|
flags_given: "helpful flags"
|
||||||
|
|
|
@ -650,9 +650,16 @@ en:
|
||||||
authorize: "Authorize"
|
authorize: "Authorize"
|
||||||
read: "read"
|
read: "read"
|
||||||
read_write: "read/write"
|
read_write: "read/write"
|
||||||
description: "Would you like to grant \"%{application_name}\" %{access} access to your account?"
|
description: "\"%{application_name}\" is requesting the following access to your account:"
|
||||||
no_trust_level: "Sorry, you do not have the required trust level to access the user API"
|
no_trust_level: "Sorry, you do not have the required trust level to access the user API"
|
||||||
generic_error: "Sorry, we are unable to issue user API keys, this feature may be disabled by the site admin"
|
generic_error: "Sorry, we are unable to issue user API keys, this feature may be disabled by the site admin"
|
||||||
|
scopes:
|
||||||
|
message_bus: "Live updates"
|
||||||
|
notifications: "Read and clear notifications"
|
||||||
|
push: "Push notifications to external services"
|
||||||
|
session_info: "Read user session info"
|
||||||
|
read: "Read all"
|
||||||
|
write: "Write all"
|
||||||
|
|
||||||
reports:
|
reports:
|
||||||
visits:
|
visits:
|
||||||
|
@ -1387,9 +1394,8 @@ en:
|
||||||
|
|
||||||
max_user_api_reqs_per_day: "Maximum number of user API requests per key per day"
|
max_user_api_reqs_per_day: "Maximum number of user API requests per key per day"
|
||||||
max_user_api_reqs_per_minute: "Maximum number of user API requests per key per minute"
|
max_user_api_reqs_per_minute: "Maximum number of user API requests per key per minute"
|
||||||
allow_read_user_api_keys: "Allow generation of readonly user API keys"
|
allow_user_api_keys: "Allow generation of user API keys"
|
||||||
allow_write_user_api_keys: "Allow generation of write user API keys"
|
allow_user_api_key_scopes: "List of scopes allowed for user API keys"
|
||||||
allow_push_user_api_keys: "Allow generation of push user API keys"
|
|
||||||
max_api_keys_per_user: "Maximum number of user API keys per user"
|
max_api_keys_per_user: "Maximum number of user API keys per user"
|
||||||
min_trust_level_for_user_api_key: "Trust level required for generation of user API keys"
|
min_trust_level_for_user_api_key: "Trust level required for generation of user API keys"
|
||||||
allowed_user_api_auth_redirects: "Allowed URL for authentication redirect for user API keys"
|
allowed_user_api_auth_redirects: "Allowed URL for authentication redirect for user API keys"
|
||||||
|
|
|
@ -1287,12 +1287,11 @@ user_api:
|
||||||
default: 2880
|
default: 2880
|
||||||
max_user_api_reqs_per_minute:
|
max_user_api_reqs_per_minute:
|
||||||
default: 20
|
default: 20
|
||||||
allow_read_user_api_keys:
|
allow_user_api_keys:
|
||||||
default: true
|
|
||||||
allow_write_user_api_keys:
|
|
||||||
default: true
|
|
||||||
allow_push_user_api_keys:
|
|
||||||
default: true
|
default: true
|
||||||
|
allow_user_api_key_scopes:
|
||||||
|
default: 'read|write|message_bus|push|notifications|session_info'
|
||||||
|
type: list
|
||||||
max_api_keys_per_user:
|
max_api_keys_per_user:
|
||||||
default: 10
|
default: 10
|
||||||
push_api_secret_key:
|
push_api_secret_key:
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
class AddScopesToUserApiKeys < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :user_api_keys, :scopes, :text, array: true, null: false, default: []
|
||||||
|
|
||||||
|
execute "UPDATE user_api_keys SET scopes = scopes || ARRAY['write'] WHERE write"
|
||||||
|
execute "UPDATE user_api_keys SET scopes = scopes || ARRAY['read'] WHERE read"
|
||||||
|
execute "UPDATE user_api_keys SET scopes = scopes || ARRAY['push'] WHERE push"
|
||||||
|
|
||||||
|
remove_column :user_api_keys, :read
|
||||||
|
remove_column :user_api_keys, :write
|
||||||
|
remove_column :user_api_keys, :push
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,6 +6,7 @@ class Auth::DefaultCurrentUserProvider
|
||||||
CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER".freeze
|
CURRENT_USER_KEY ||= "_DISCOURSE_CURRENT_USER".freeze
|
||||||
API_KEY ||= "api_key".freeze
|
API_KEY ||= "api_key".freeze
|
||||||
USER_API_KEY ||= "HTTP_USER_API_KEY".freeze
|
USER_API_KEY ||= "HTTP_USER_API_KEY".freeze
|
||||||
|
USER_API_CLIENT_ID ||= "HTTP_USER_API_CLIENT_ID".freeze
|
||||||
API_KEY_ENV ||= "_DISCOURSE_API".freeze
|
API_KEY_ENV ||= "_DISCOURSE_API".freeze
|
||||||
TOKEN_COOKIE ||= "_t".freeze
|
TOKEN_COOKIE ||= "_t".freeze
|
||||||
PATH_INFO ||= "PATH_INFO".freeze
|
PATH_INFO ||= "PATH_INFO".freeze
|
||||||
|
@ -90,7 +91,7 @@ class Auth::DefaultCurrentUserProvider
|
||||||
limiter_min.performed!
|
limiter_min.performed!
|
||||||
end
|
end
|
||||||
|
|
||||||
current_user = lookup_user_api_user(api_key)
|
current_user = lookup_user_api_user_and_update_key(api_key, @env[USER_API_CLIENT_ID])
|
||||||
raise Discourse::InvalidAccess unless current_user
|
raise Discourse::InvalidAccess unless current_user
|
||||||
|
|
||||||
limiter_min.performed!
|
limiter_min.performed!
|
||||||
|
@ -176,16 +177,14 @@ class Auth::DefaultCurrentUserProvider
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
WHITELISTED_WRITE_PATHS ||= [/^\/message-bus\/.*\/poll/, /^\/user-api-key\/revoke$/]
|
def lookup_user_api_user_and_update_key(user_api_key, client_id)
|
||||||
def lookup_user_api_user(user_api_key)
|
|
||||||
if api_key = UserApiKey.where(key: user_api_key, revoked_at: nil).includes(:user).first
|
if api_key = UserApiKey.where(key: user_api_key, revoked_at: nil).includes(:user).first
|
||||||
unless api_key.write
|
unless api_key.allow?(@env)
|
||||||
if @env["REQUEST_METHOD"] != "GET"
|
|
||||||
path = @env["PATH_INFO"]
|
|
||||||
unless WHITELISTED_WRITE_PATHS.any?{|whitelisted| path =~ whitelisted}
|
|
||||||
raise Discourse::InvalidAccess
|
raise Discourse::InvalidAccess
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
if client_id.present? && client_id != api_key.client_id
|
||||||
|
api_key.update_columns(client_id: client_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
api_key.user
|
api_key.user
|
||||||
|
|
|
@ -163,9 +163,7 @@ describe Auth::DefaultCurrentUserProvider do
|
||||||
UserApiKey.create!(
|
UserApiKey.create!(
|
||||||
application_name: 'my app',
|
application_name: 'my app',
|
||||||
client_id: '1234',
|
client_id: '1234',
|
||||||
read: true,
|
scopes: ['read'],
|
||||||
write: false,
|
|
||||||
push: false,
|
|
||||||
key: SecureRandom.hex,
|
key: SecureRandom.hex,
|
||||||
user_id: user.id
|
user_id: user.id
|
||||||
)
|
)
|
||||||
|
|
|
@ -35,7 +35,7 @@ TXT
|
||||||
|
|
||||||
let :args do
|
let :args do
|
||||||
{
|
{
|
||||||
access: 'r',
|
scopes: 'read',
|
||||||
client_id: "x"*32,
|
client_id: "x"*32,
|
||||||
auth_redirect: 'http://over.the/rainbow',
|
auth_redirect: 'http://over.the/rainbow',
|
||||||
application_name: 'foo',
|
application_name: 'foo',
|
||||||
|
@ -48,7 +48,7 @@ TXT
|
||||||
it "supports a head request cleanly" do
|
it "supports a head request cleanly" do
|
||||||
head :new
|
head :new
|
||||||
expect(response.code).to eq("200")
|
expect(response.code).to eq("200")
|
||||||
expect(response.headers["Auth-Api-Version"]).to eq("1")
|
expect(response.headers["Auth-Api-Version"]).to eq("2")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -67,7 +67,6 @@ TXT
|
||||||
end
|
end
|
||||||
|
|
||||||
it "will allow tokens for staff without TL" do
|
it "will allow tokens for staff without TL" do
|
||||||
|
|
||||||
SiteSetting.min_trust_level_for_user_api_key = 2
|
SiteSetting.min_trust_level_for_user_api_key = 2
|
||||||
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ TXT
|
||||||
|
|
||||||
SiteSetting.min_trust_level_for_user_api_key = 0
|
SiteSetting.min_trust_level_for_user_api_key = 0
|
||||||
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
||||||
SiteSetting.allow_read_user_api_keys = false
|
SiteSetting.allow_user_api_key_scopes = "write"
|
||||||
|
|
||||||
user = Fabricate(:user, trust_level: 0)
|
user = Fabricate(:user, trust_level: 0)
|
||||||
|
|
||||||
|
@ -143,7 +142,7 @@ TXT
|
||||||
SiteSetting.min_trust_level_for_user_api_key = 0
|
SiteSetting.min_trust_level_for_user_api_key = 0
|
||||||
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
||||||
|
|
||||||
args[:access] = "pr"
|
args[:scopes] = "push,read"
|
||||||
args[:push_url] = "https://push.it/here"
|
args[:push_url] = "https://push.it/here"
|
||||||
|
|
||||||
user = Fabricate(:user, trust_level: 0)
|
user = Fabricate(:user, trust_level: 0)
|
||||||
|
@ -164,22 +163,21 @@ TXT
|
||||||
parsed = JSON.parse(key.private_decrypt(encrypted))
|
parsed = JSON.parse(key.private_decrypt(encrypted))
|
||||||
|
|
||||||
expect(parsed["nonce"]).to eq(args[:nonce])
|
expect(parsed["nonce"]).to eq(args[:nonce])
|
||||||
expect(parsed["access"].split('').sort).to eq(['r'])
|
expect(parsed["push"]).to eq(false)
|
||||||
|
expect(parsed["api"]).to eq(2)
|
||||||
|
|
||||||
key = user.user_api_keys.first
|
key = user.user_api_keys.first
|
||||||
expect(key.push).to eq(true)
|
expect(key.scopes).to include("push")
|
||||||
expect(key.push_url).to eq("https://push.it/here")
|
expect(key.push_url).to eq("https://push.it/here")
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "will redirect correctly with valid token" do
|
it "will redirect correctly with valid token" do
|
||||||
|
|
||||||
SiteSetting.min_trust_level_for_user_api_key = 0
|
SiteSetting.min_trust_level_for_user_api_key = 0
|
||||||
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
||||||
SiteSetting.allowed_user_api_push_urls = "https://push.it/here"
|
SiteSetting.allowed_user_api_push_urls = "https://push.it/here"
|
||||||
SiteSetting.allow_write_user_api_keys = true
|
|
||||||
|
|
||||||
args[:access] = "prw"
|
args[:scopes] = "push,notifications,message_bus,session_info"
|
||||||
args[:push_url] = "https://push.it/here"
|
args[:push_url] = "https://push.it/here"
|
||||||
|
|
||||||
user = Fabricate(:user, trust_level: 0)
|
user = Fabricate(:user, trust_level: 0)
|
||||||
|
@ -200,14 +198,12 @@ TXT
|
||||||
parsed = JSON.parse(key.private_decrypt(encrypted))
|
parsed = JSON.parse(key.private_decrypt(encrypted))
|
||||||
|
|
||||||
expect(parsed["nonce"]).to eq(args[:nonce])
|
expect(parsed["nonce"]).to eq(args[:nonce])
|
||||||
expect(parsed["access"].split('').sort).to eq(['p','r', 'w'])
|
expect(parsed["push"]).to eq(true)
|
||||||
|
|
||||||
api_key = UserApiKey.find_by(key: parsed["key"])
|
api_key = UserApiKey.find_by(key: parsed["key"])
|
||||||
|
|
||||||
expect(api_key.user_id).to eq(user.id)
|
expect(api_key.user_id).to eq(user.id)
|
||||||
expect(api_key.read).to eq(true)
|
expect(api_key.scopes.sort).to eq(["push", "message_bus", "notifications", "session_info"].sort)
|
||||||
expect(api_key.write).to eq(true)
|
|
||||||
expect(api_key.push).to eq(true)
|
|
||||||
expect(api_key.push_url).to eq("https://push.it/here")
|
expect(api_key.push_url).to eq("https://push.it/here")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
Fabricator(:readonly_user_api_key, from: :user_api_key) do
|
Fabricator(:readonly_user_api_key, from: :user_api_key) do
|
||||||
user
|
user
|
||||||
read true
|
scopes ['read']
|
||||||
write false
|
|
||||||
push false
|
|
||||||
client_id { SecureRandom.hex }
|
client_id { SecureRandom.hex }
|
||||||
key { SecureRandom.hex }
|
key { SecureRandom.hex }
|
||||||
application_name 'some app'
|
application_name 'some app'
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe UserApiKey do
|
||||||
|
context "#allow?" do
|
||||||
|
it "can look up permissions correctly" do
|
||||||
|
key = UserApiKey.new(scopes: ['message_bus', 'notifications'])
|
||||||
|
|
||||||
|
expect(key.allow?("PATH_INFO" => "/random", "REQUEST_METHOD" => "GET")).to eq(false)
|
||||||
|
expect(key.allow?("PATH_INFO" => "/message-bus/1234/poll", "REQUEST_METHOD" => "POST")).to eq(true)
|
||||||
|
|
||||||
|
expect(key.allow?("action_dispatch.request.path_parameters" => {:controller => "notifications", :action => "mark_read"},
|
||||||
|
"PATH_INFO" => "/xyz", "REQUEST_METHOD" => "PUT")).to eq(true)
|
||||||
|
|
||||||
|
|
||||||
|
expect(key.allow?("action_dispatch.request.path_parameters" => {:controller => "user_api_keys", :action => "revoke"},
|
||||||
|
"PATH_INFO" => "/xyz", "REQUEST_METHOD" => "POST")).to eq(true)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can allow blanket read" do
|
||||||
|
|
||||||
|
key = UserApiKey.new(scopes: ['read'])
|
||||||
|
|
||||||
|
expect(key.allow?("PATH_INFO" => "/random", "REQUEST_METHOD" => "GET")).to eq(true)
|
||||||
|
expect(key.allow?("PATH_INFO" => "/random", "REQUEST_METHOD" => "PUT")).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -338,9 +338,7 @@ describe PostAlerter do
|
||||||
client_id: "xxx#{i}",
|
client_id: "xxx#{i}",
|
||||||
key: "yyy#{i}",
|
key: "yyy#{i}",
|
||||||
application_name: "iPhone#{i}",
|
application_name: "iPhone#{i}",
|
||||||
read: true,
|
scopes: ['push'],
|
||||||
write: true,
|
|
||||||
push: true,
|
|
||||||
push_url: "https://site2.com/push")
|
push_url: "https://site2.com/push")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue