mirror of
https://github.com/codeninjasllc/discourse.git
synced 2025-04-30 07:53:57 -04:00
Feature: User API key support (server side implementation)
- Supports throttled read and write - No support for push yet, but data is captured about intent
This commit is contained in:
parent
d3c8985030
commit
fc095acaaa
10 changed files with 396 additions and 0 deletions
app
config
db/migrate
lib/auth
spec
68
app/controllers/user_api_keys_controller.rb
Normal file
68
app/controllers/user_api_keys_controller.rb
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
class UserApiKeysController < ApplicationController
|
||||||
|
|
||||||
|
skip_before_filter :redirect_to_login_if_required, only: [:new]
|
||||||
|
skip_before_filter :check_xhr
|
||||||
|
before_filter :ensure_logged_in, only: [:create]
|
||||||
|
|
||||||
|
def new
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
|
||||||
|
[
|
||||||
|
:public_key,
|
||||||
|
:nonce,
|
||||||
|
:access,
|
||||||
|
:client_id,
|
||||||
|
:auth_redirect,
|
||||||
|
:application_name
|
||||||
|
].each{|p| params.require(p)}
|
||||||
|
|
||||||
|
unless SiteSetting.allowed_user_api_auth_redirects
|
||||||
|
.split('|')
|
||||||
|
.any?{|u| params[:auth_redirect] == u}
|
||||||
|
|
||||||
|
raise Discourse::InvalidAccess
|
||||||
|
end
|
||||||
|
|
||||||
|
raise Discourse::InvalidAccess if current_user.trust_level < SiteSetting.min_trust_level_for_user_api_key
|
||||||
|
|
||||||
|
request_read = params[:access].include? 'r'
|
||||||
|
request_push = params[:access].include? 'p'
|
||||||
|
request_write = params[:access].include? 'w'
|
||||||
|
|
||||||
|
raise Discourse::InvalidAccess unless request_read || request_push
|
||||||
|
raise Discourse::InvalidAccess if request_read && !SiteSetting.allow_read_user_api_keys
|
||||||
|
raise Discourse::InvalidAccess if request_write && !SiteSetting.allow_write_user_api_keys
|
||||||
|
raise Discourse::InvalidAccess if request_push && !SiteSetting.allow_push_user_api_keys
|
||||||
|
|
||||||
|
if request_push && !SiteSetting.allowed_user_api_push_urls.split('|').any?{|u| params[:push_url] == u}
|
||||||
|
raise Discourse::InvalidAccess
|
||||||
|
end
|
||||||
|
|
||||||
|
key = UserApiKey.create!(
|
||||||
|
application_name: params[:application_name],
|
||||||
|
client_id: params[:client_id],
|
||||||
|
read: request_read,
|
||||||
|
push: request_push,
|
||||||
|
user_id: current_user.id,
|
||||||
|
write: request_write,
|
||||||
|
key: SecureRandom.hex,
|
||||||
|
push_url: request_push ? params[:push_url] : nil
|
||||||
|
)
|
||||||
|
|
||||||
|
# we keep the payload short so it encrypts easily with public key
|
||||||
|
# it is often restricted to 128 chars
|
||||||
|
payload = {
|
||||||
|
key: key.key,
|
||||||
|
nonce: params[:nonce],
|
||||||
|
access: key.access
|
||||||
|
}.to_json
|
||||||
|
|
||||||
|
public_key = OpenSSL::PKey::RSA.new(params[:public_key])
|
||||||
|
payload = Base64.encode64(public_key.public_encrypt(payload))
|
||||||
|
|
||||||
|
redirect_to "#{params[:auth_redirect]}?payload=#{CGI.escape(payload)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
30
app/models/user_api_key.rb
Normal file
30
app/models/user_api_key.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
class UserApiKey < ActiveRecord::Base
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
def access
|
||||||
|
"#{read ? "r" : ""}#{write ? "w" : ""}#{push ? "p" : ""}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: user_api_keys
|
||||||
|
#
|
||||||
|
# id :integer not null, primary key
|
||||||
|
# user_id :integer not null
|
||||||
|
# client_id :string not null
|
||||||
|
# key :string not null
|
||||||
|
# application_name :string not null
|
||||||
|
# read :boolean not null
|
||||||
|
# write :boolean not null
|
||||||
|
# push :boolean not null
|
||||||
|
# push_url :string
|
||||||
|
# created_at :datetime
|
||||||
|
# updated_at :datetime
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_user_api_keys_on_client_id (client_id)
|
||||||
|
# index_user_api_keys_on_key (key) UNIQUE
|
||||||
|
# index_user_api_keys_on_user_id (user_id)
|
||||||
|
#
|
|
@ -3006,6 +3006,7 @@ en:
|
||||||
developer: 'Developer'
|
developer: 'Developer'
|
||||||
embedding: "Embedding"
|
embedding: "Embedding"
|
||||||
legal: "Legal"
|
legal: "Legal"
|
||||||
|
user_api: 'User API'
|
||||||
uncategorized: 'Other'
|
uncategorized: 'Other'
|
||||||
backups: "Backups"
|
backups: "Backups"
|
||||||
login: "Login"
|
login: "Login"
|
||||||
|
|
|
@ -1365,6 +1365,16 @@ en:
|
||||||
default_categories_tracking: "List of categories that are tracked by default."
|
default_categories_tracking: "List of categories that are tracked by default."
|
||||||
default_categories_muted: "List of categories that are muted by default."
|
default_categories_muted: "List of categories that are muted by default."
|
||||||
|
|
||||||
|
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"
|
||||||
|
allow_read_user_api_keys: "Allow generation of readonly user API keys"
|
||||||
|
allow_write_user_api_keys: "Allow generation of write 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"
|
||||||
|
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_push_urls: "Allowed URLs for server push to user API"
|
||||||
|
|
||||||
tagging_enabled: "Enable tags on topics?"
|
tagging_enabled: "Enable tags on topics?"
|
||||||
min_trust_to_create_tag: "The minimum trust level required to create a tag."
|
min_trust_to_create_tag: "The minimum trust level required to create a tag."
|
||||||
max_tags_per_topic: "The maximum tags that can be applied to a topic."
|
max_tags_per_topic: "The maximum tags that can be applied to a topic."
|
||||||
|
|
|
@ -661,5 +661,9 @@ Discourse::Application.routes.draw do
|
||||||
# special case for top
|
# special case for top
|
||||||
root to: "list#top", constraints: HomePageConstraint.new("top"), :as => "top_lists"
|
root to: "list#top", constraints: HomePageConstraint.new("top"), :as => "top_lists"
|
||||||
|
|
||||||
|
get "/user-api-key/new" => "user_api_keys#new"
|
||||||
|
post "/user-api-key/new" => "user_api_keys#create"
|
||||||
|
|
||||||
get "*url", to: 'permalinks#show', constraints: PermalinkConstraint.new
|
get "*url", to: 'permalinks#show', constraints: PermalinkConstraint.new
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1243,6 +1243,28 @@ user_preferences:
|
||||||
type: category_list
|
type: category_list
|
||||||
default: ''
|
default: ''
|
||||||
|
|
||||||
|
user_api:
|
||||||
|
max_user_api_reqs_per_day:
|
||||||
|
default: 2880
|
||||||
|
max_user_api_reqs_per_minute:
|
||||||
|
default: 12
|
||||||
|
allow_read_user_api_keys:
|
||||||
|
default: true
|
||||||
|
allow_write_user_api_keys:
|
||||||
|
default: false
|
||||||
|
allow_push_user_api_keys:
|
||||||
|
default: true
|
||||||
|
max_api_keys_per_user:
|
||||||
|
default: 10
|
||||||
|
min_trust_level_for_user_api_key:
|
||||||
|
default: 1
|
||||||
|
allowed_user_api_push_urls:
|
||||||
|
default: ''
|
||||||
|
type: list
|
||||||
|
allowed_user_api_auth_redirects:
|
||||||
|
default: 'https://api.discourse.org/api/auth_redirect'
|
||||||
|
type: list
|
||||||
|
|
||||||
tags:
|
tags:
|
||||||
tagging_enabled:
|
tagging_enabled:
|
||||||
client: true
|
client: true
|
||||||
|
|
19
db/migrate/20160815002002_add_user_api_keys.rb
Normal file
19
db/migrate/20160815002002_add_user_api_keys.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
class AddUserApiKeys < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :user_api_keys do |t|
|
||||||
|
t.integer :user_id, null: false
|
||||||
|
t.string :client_id, null: false
|
||||||
|
t.string :key, null: false
|
||||||
|
t.string :application_name, null: false
|
||||||
|
t.boolean :read, null: false
|
||||||
|
t.boolean :write, null: false
|
||||||
|
t.boolean :push, null: false
|
||||||
|
t.string :push_url
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :user_api_keys, [:key], unique: true
|
||||||
|
add_index :user_api_keys, [:user_id]
|
||||||
|
add_index :user_api_keys, [:client_id]
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,6 +5,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 ||= "USER_API_KEY".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
|
||||||
|
@ -75,10 +76,35 @@ class Auth::DefaultCurrentUserProvider
|
||||||
@env[API_KEY_ENV] = true
|
@env[API_KEY_ENV] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# user api key handling
|
||||||
|
if api_key = @env[USER_API_KEY]
|
||||||
|
|
||||||
|
limiter_min = RateLimiter.new(nil, "user_api_min_#{api_key}", SiteSetting.max_user_api_reqs_per_minute, 60)
|
||||||
|
limiter_day = RateLimiter.new(nil, "user_api_day_#{api_key}", SiteSetting.max_user_api_reqs_per_day, 86400)
|
||||||
|
|
||||||
|
unless limiter_day.can_perform?
|
||||||
|
raise RateLimiter::LimitExceeded, "User API calls per minute exceeded"
|
||||||
|
end
|
||||||
|
|
||||||
|
unless limiter_min.can_perform?
|
||||||
|
raise RateLimiter::LimitExceeded, "User API calls per day exceeded"
|
||||||
|
end
|
||||||
|
|
||||||
|
current_user = lookup_user_api_user(api_key)
|
||||||
|
raise Discourse::InvalidAccess unless current_user
|
||||||
|
|
||||||
|
limiter_min.performed!
|
||||||
|
limiter_day.performed!
|
||||||
|
|
||||||
|
@env[API_KEY_ENV] = true
|
||||||
|
end
|
||||||
|
|
||||||
@env[CURRENT_USER_KEY] = current_user
|
@env[CURRENT_USER_KEY] = current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
def refresh_session(user, session, cookies)
|
def refresh_session(user, session, cookies)
|
||||||
|
return if is_api?
|
||||||
|
|
||||||
if user && (!user.auth_token_updated_at || user.auth_token_updated_at <= 1.hour.ago)
|
if user && (!user.auth_token_updated_at || user.auth_token_updated_at <= 1.hour.ago)
|
||||||
user.update_column(:auth_token_updated_at, Time.zone.now)
|
user.update_column(:auth_token_updated_at, Time.zone.now)
|
||||||
cookies[TOKEN_COOKIE] = { value: user.auth_token, httponly: true, expires: SiteSetting.maximum_session_age.hours.from_now }
|
cookies[TOKEN_COOKIE] = { value: user.auth_token, httponly: true, expires: SiteSetting.maximum_session_age.hours.from_now }
|
||||||
|
@ -150,6 +176,16 @@ class Auth::DefaultCurrentUserProvider
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def lookup_user_api_user(user_api_key)
|
||||||
|
if api_key = UserApiKey.where(key: user_api_key).includes(:user).first
|
||||||
|
if !api_key.write && @env["REQUEST_METHOD"] != "GET"
|
||||||
|
raise Discourse::InvalidAccess
|
||||||
|
end
|
||||||
|
|
||||||
|
api_key.user
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def lookup_api_user(api_key_value, request)
|
def lookup_api_user(api_key_value, request)
|
||||||
if api_key = ApiKey.where(key: api_key_value).includes(:user).first
|
if api_key = ApiKey.where(key: api_key_value).includes(:user).first
|
||||||
api_username = request["api_username"]
|
api_username = request["api_username"]
|
||||||
|
|
|
@ -153,5 +153,78 @@ describe Auth::DefaultCurrentUserProvider do
|
||||||
freeze_time 3.hours.from_now
|
freeze_time 3.hours.from_now
|
||||||
expect(provider("/", "HTTP_COOKIE" => "_t=#{user.auth_token}").current_user).to eq(nil)
|
expect(provider("/", "HTTP_COOKIE" => "_t=#{user.auth_token}").current_user).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "user api" do
|
||||||
|
let :user do
|
||||||
|
Fabricate(:user)
|
||||||
|
end
|
||||||
|
|
||||||
|
let :api_key do
|
||||||
|
UserApiKey.create!(
|
||||||
|
application_name: 'my app',
|
||||||
|
client_id: '1234',
|
||||||
|
read: true,
|
||||||
|
write: false,
|
||||||
|
push: false,
|
||||||
|
key: SecureRandom.hex,
|
||||||
|
user_id: user.id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows user API access correctly" do
|
||||||
|
params = {
|
||||||
|
"REQUEST_METHOD" => "GET",
|
||||||
|
"USER_API_KEY" => api_key.key,
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(provider("/", params).current_user.id).to eq(user.id)
|
||||||
|
|
||||||
|
expect {
|
||||||
|
provider("/", params.merge({"REQUEST_METHOD" => "POST"})).current_user
|
||||||
|
}.to raise_error(Discourse::InvalidAccess)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it "rate limits api usage" do
|
||||||
|
|
||||||
|
RateLimiter.stubs(:disabled?).returns(false)
|
||||||
|
limiter1 = RateLimiter.new(nil, "user_api_day_#{api_key.key}", 10, 60)
|
||||||
|
limiter2 = RateLimiter.new(nil, "user_api_min_#{api_key.key}", 10, 60)
|
||||||
|
limiter1.clear!
|
||||||
|
limiter2.clear!
|
||||||
|
|
||||||
|
SiteSetting.max_user_api_reqs_per_day = 3
|
||||||
|
SiteSetting.max_user_api_reqs_per_minute = 4
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"REQUEST_METHOD" => "GET",
|
||||||
|
"USER_API_KEY" => api_key.key,
|
||||||
|
}
|
||||||
|
|
||||||
|
3.times do
|
||||||
|
provider("/", params).current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
expect {
|
||||||
|
provider("/", params).current_user
|
||||||
|
}.to raise_error(RateLimiter::LimitExceeded)
|
||||||
|
|
||||||
|
|
||||||
|
SiteSetting.max_user_api_reqs_per_day = 4
|
||||||
|
SiteSetting.max_user_api_reqs_per_minute = 3
|
||||||
|
|
||||||
|
limiter1.clear!
|
||||||
|
limiter2.clear!
|
||||||
|
|
||||||
|
3.times do
|
||||||
|
provider("/", params).current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
expect {
|
||||||
|
provider("/", params).current_user
|
||||||
|
}.to raise_error(RateLimiter::LimitExceeded)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
133
spec/controllers/user_api_keys_controller_spec.rb
Normal file
133
spec/controllers/user_api_keys_controller_spec.rb
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe UserApiKeysController do
|
||||||
|
|
||||||
|
let :public_key do
|
||||||
|
<<TXT
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDh7BS7Ey8hfbNhlNAW/47pqT7w
|
||||||
|
IhBz3UyBYzin8JurEQ2pY9jWWlY8CH147KyIZf1fpcsi7ZNxGHeDhVsbtUKZxnFV
|
||||||
|
p16Op3CHLJnnJKKBMNdXMy0yDfCAHZtqxeBOTcCo1Vt/bHpIgiK5kmaekyXIaD0n
|
||||||
|
w0z/BYpOgZ8QwnI5ZwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
TXT
|
||||||
|
end
|
||||||
|
|
||||||
|
let :private_key do
|
||||||
|
<<TXT
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICWwIBAAKBgQDh7BS7Ey8hfbNhlNAW/47pqT7wIhBz3UyBYzin8JurEQ2pY9jW
|
||||||
|
WlY8CH147KyIZf1fpcsi7ZNxGHeDhVsbtUKZxnFVp16Op3CHLJnnJKKBMNdXMy0y
|
||||||
|
DfCAHZtqxeBOTcCo1Vt/bHpIgiK5kmaekyXIaD0nw0z/BYpOgZ8QwnI5ZwIDAQAB
|
||||||
|
AoGAeHesbjzCivc+KbBybXEEQbBPsThY0Y+VdgD0ewif2U4UnNhzDYnKJeTZExwQ
|
||||||
|
vAK2YsRDV3KbhljnkagQduvmgJyCKuV/CxZvbJddwyIs3+U2D4XysQp3e1YZ7ROr
|
||||||
|
YlOIoekHCx1CNm6A4iImqGxB0aJ7Owdk3+QSIaMtGQWaPTECQQDz2UjJ+bomguNs
|
||||||
|
zdcv3ZP7W3U5RG+TpInSHiJXpt2JdNGfHItozGJCxfzDhuKHK5Cb23bgldkvB9Xc
|
||||||
|
p/tngTtNAkEA7S4cqUezA82xS7aYPehpRkKEmqzMwR3e9WeL7nZ2cdjZAHgXe49l
|
||||||
|
3mBhidEyRmtPqbXo1Xix8LDuqik0IdnlgwJAQeYTnLnHS8cNjQbnw4C/ECu8Nzi+
|
||||||
|
aokJ0eXg5A0tS4ttZvGA31Z0q5Tz5SdbqqnkT6p0qub0JZiZfCNNdsBe9QJAaGT5
|
||||||
|
fJDwfGYW+YpfLDCV1bUFhMc2QHITZtSyxL0jmSynJwu02k/duKmXhP+tL02gfMRy
|
||||||
|
vTMorxZRllgYeCXeXQJAEGRXR8/26jwqPtKKJzC7i9BuOYEagqj0nLG2YYfffCMc
|
||||||
|
d3JGCf7DMaUlaUE8bJ08PtHRJFSGkNfDJLhLKSjpbw==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
TXT
|
||||||
|
end
|
||||||
|
|
||||||
|
let :args do
|
||||||
|
{
|
||||||
|
access: 'r',
|
||||||
|
client_id: "x"*32,
|
||||||
|
auth_redirect: 'http://over.the/rainbow',
|
||||||
|
application_name: 'foo',
|
||||||
|
public_key: public_key,
|
||||||
|
nonce: SecureRandom.hex
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'create' do
|
||||||
|
|
||||||
|
it "does not allow anon" do
|
||||||
|
expect {
|
||||||
|
post :create, args
|
||||||
|
}.to raise_error(Discourse::NotLoggedIn)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "refuses to redirect to disallowed place" do
|
||||||
|
log_in_user(Fabricate(:user))
|
||||||
|
post :create, args
|
||||||
|
expect(response.code).to eq("403")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "will not create token unless TL is met" do
|
||||||
|
SiteSetting.min_trust_level_for_user_api_key = 2
|
||||||
|
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
||||||
|
|
||||||
|
user = Fabricate(:user, trust_level: 1)
|
||||||
|
|
||||||
|
log_in_user(user)
|
||||||
|
|
||||||
|
post :create, args
|
||||||
|
expect(response.code).to eq("403")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it "will deny access if requesting more rights than allowed" do
|
||||||
|
|
||||||
|
SiteSetting.min_trust_level_for_user_api_key = 0
|
||||||
|
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
||||||
|
SiteSetting.allow_read_user_api_keys = false
|
||||||
|
|
||||||
|
user = Fabricate(:user, trust_level: 0)
|
||||||
|
|
||||||
|
log_in_user(user)
|
||||||
|
|
||||||
|
post :create, args
|
||||||
|
expect(response.code).to eq("403")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it "will redirect correctly with valid token" do
|
||||||
|
|
||||||
|
SiteSetting.min_trust_level_for_user_api_key = 0
|
||||||
|
SiteSetting.allowed_user_api_auth_redirects = args[:auth_redirect]
|
||||||
|
SiteSetting.allowed_user_api_push_urls = "https://push.it/here"
|
||||||
|
SiteSetting.allow_write_user_api_keys = true
|
||||||
|
|
||||||
|
args[:access] = "prw"
|
||||||
|
args[:push_url] = "https://push.it/here"
|
||||||
|
|
||||||
|
user = Fabricate(:user, trust_level: 0)
|
||||||
|
|
||||||
|
log_in_user(user)
|
||||||
|
|
||||||
|
post :create, args
|
||||||
|
expect(response.code).to eq("302")
|
||||||
|
|
||||||
|
uri = URI.parse(response.redirect_url)
|
||||||
|
|
||||||
|
query = uri.query
|
||||||
|
payload = query.split("payload=")[1]
|
||||||
|
encrypted = Base64.decode64(CGI.unescape(payload))
|
||||||
|
|
||||||
|
key = OpenSSL::PKey::RSA.new(private_key)
|
||||||
|
|
||||||
|
parsed = JSON.parse(key.private_decrypt(encrypted))
|
||||||
|
|
||||||
|
expect(parsed["nonce"]).to eq(args[:nonce])
|
||||||
|
|
||||||
|
api_key = UserApiKey.find_by(key: parsed["key"])
|
||||||
|
|
||||||
|
expect(api_key.user_id).to eq(user.id)
|
||||||
|
expect(api_key.read).to eq(true)
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
uri.query = ""
|
||||||
|
expect(uri.to_s).to eq(args[:auth_redirect] + "?")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Add a link
Reference in a new issue