Revert "Add core API using JWT authentication and tests for it [SCI-1507]"

This commit is contained in:
Luka Murn 2017-08-07 11:54:45 +02:00 committed by GitHub
parent f92641af14
commit 13eae287bb
16 changed files with 1 additions and 447 deletions

View file

@ -17,9 +17,6 @@ gem 'font-awesome-rails', '~> 4.7.0.2'
gem 'recaptcha', require: 'recaptcha/rails'
gem 'sanitize', '~> 4.4'
# Gems for API implementation
gem 'jwt'
# JS datetime library, requirement of datetime picker
gem 'momentjs-rails', '~> 2.17.1'
# JS datetime picker

View file

@ -210,7 +210,6 @@ GEM
js_cookie_rails (2.1.4)
railties (>= 3.1)
json (1.8.6)
jwt (1.5.6)
kaminari (1.0.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.0.1)
@ -489,7 +488,6 @@ DEPENDENCIES
jquery-turbolinks
jquery-ui-rails
js_cookie_rails
jwt
kaminari
listen (~> 3.0)
logging (~> 2.0.0)

View file

@ -1,84 +0,0 @@
module Api
class ApiController < ActionController::API
attr_reader :authorized
attr_reader :iss
attr_reader :token
attr_reader :current_user
before_action :load_token, except: %i(authenticate status)
before_action :load_iss, except: %i(authenticate status)
before_action :authenticate_request!, except: %i(authenticate status)
rescue_from StandardError do
render json: {}, status: :bad_request
end
rescue_from JWT::InvalidPayload, JWT::DecodeError do
render json: { message: I18n.t('api.core.invalid_token') },
status: :forbidden
end
def initialize
super
@iss = nil
@authorized = false
end
def status
response = {}
response[:message] = I18n.t('api.core.status_ok')
response[:versions] = []
Extends::API_VERSIONS.each do |ver|
response[:versions] << { version: ver, baseUrl: "/api/#{ver}/" }
end
render json: response, status: :ok
end
def authenticate
if auth_params[:grant_type] == 'password'
user = User.find_by_email(auth_params[:email])
raise StandardError unless user &&
user.valid_password?(auth_params[:password])
payload = { user_id: user.id }
token = CoreJwt.encode(payload)
render json: { token_type: 'bearer', access_token: token }
else
raise StandardError
end
end
private
def load_token
@token =
request.headers['Authorization'].scan(/Bearer (.*)$/).flatten.last
raise StandardError unless @token
end
def authenticate_request!
return true if authorized
# Check request header for proper auth token
payload = CoreJwt.decode(token)
@current_user = User.find_by_id(payload['user_id'])
# Implement sliding sessions, i.e send new token in case of successful
# authorization and when tokens TTL reached specific value (to avoid token
# generation on each request)
if CoreJwt.refresh_needed?(payload)
new_token = CoreJwt.encode(user_id: @current_user.id)
response.headers['X-Access-Token'] = new_token
end
rescue JWT::ExpiredSignature
render json: { message: I18n.t('api.core.expired_token') },
status: :unauthorized
end
def load_iss
@iss = CoreJwt.read_iss(token)
raise JWT::InvalidPayload unless @iss
end
def auth_params
params.permit(:grant_type, :email, :password)
end
end
end

View file

@ -1,53 +0,0 @@
module Api
module V20170715
class CoreApiController < ApiController
include PermissionHelper
def tasks_tree
teams_json = []
current_user.teams.find_each do |tm|
team = tm.as_json(only: %i(name description))
team['team_id'] = tm.id.to_s
projects = []
tm.projects.find_each do |pr|
project = pr.as_json(only: %i(name visibility archived))
project['project_id'] = pr.id.to_s
experiments = []
pr.experiments.find_each do |exp|
experiment = exp.as_json(only: %i(name description archived))
experiment['experiment_id'] = exp.id.to_s
tasks = []
exp.my_modules.find_each do |tk|
task = tk.as_json(only: %i(name description archived))
task['task_id'] = tk.id.to_s
tasks << task
end
experiment['tasks'] = tasks
experiments << experiment
end
project['experiments'] = experiments
projects << project
end
team['projects'] = projects
teams_json << team
end
render json: teams_json, status: :ok
end
def task_samples
task = MyModule.find_by_id(params[:task_id])
return render json: {}, status: :not_found unless task
return render json: {}, status: :forbidden unless can_view_module(task)
samples = task.samples
samples_json = []
samples.find_each do |s|
sample = {}
sample['sample_id'] = s.id.to_s
sample['name'] = s.name
samples_json << sample
end
render json: samples_json, status: :ok
end
end
end
end

View file

@ -1,25 +0,0 @@
module Api
class << self
attr_accessor :configuration
end
def self.configuration
@configuration ||= Configuration.new
end
def self.configure
yield(configuration)
end
class Configuration
attr_accessor :core_api_sign_alg
attr_accessor :core_api_token_ttl
attr_accessor :core_api_token_iss
def initialize
@core_api_sign_alg = 'HS256'
@core_api_token_ttl = 30.minutes
@core_api_token_iss = 'SciNote'
end
end
end

View file

@ -1,32 +0,0 @@
module Api
class CoreJwt
require 'jwt'
KEY_SECRET = Rails.application.secrets.secret_key_base
def self.encode(payload, expires_at = nil)
if expires_at
payload[:exp] = expires_at
else
payload[:exp] = Api.configuration.core_api_token_ttl.from_now.to_i
end
payload[:iss] = Api.configuration.core_api_token_iss
JWT.encode(payload, KEY_SECRET, Api.configuration.core_api_sign_alg)
end
def self.decode(token)
HashWithIndifferentAccess.new(
JWT.decode(token, KEY_SECRET, Api.configuration.core_api_sign_alg)[0]
)
end
def self.read_iss(token)
JWT.decode(token, nil, false)[0][:iss].to_s
end
def self.refresh_needed?(payload)
time_left = payload[:exp].to_i - Time.now.to_i
return true if time_left < (Api.configuration.core_api_token_ttl.to_i / 2)
false
end
end
end

View file

@ -1,11 +0,0 @@
Api.configure do |config|
if ENV['CORE_API_SIGN_ALG']
config.core_api_sign_alg = ENV['CORE_API_SIGN_ALG']
end
if ENV['CORE_API_TOKEN_TTL']
config.core_api_token_ttl = ENV['CORE_API_TOKEN_TTL']
end
if ENV['CORE_API_TOKEN_ISS']
config.core_api_token_iss = ENV['CORE_API_TOKEN_ISS']
end
end

View file

@ -42,7 +42,4 @@ class Extends
# Data type name should match corresponding model's name
REPOSITORY_DATA_TYPES = { RepositoryTextValue: 0,
RepositoryDateValue: 1 }
# List of implemented core API versions
API_VERSIONS = ['20170715']
end

View file

@ -1786,12 +1786,6 @@ en:
busy: "The server is still processing your request. If you leave this page, the changes will be lost! Are you sure you want to continue?"
no_name: "(no name)"
api:
core:
status_ok: "Ok"
expired_token: "Token is expired"
invalid_token: "Token is invalid"
Add: "Add"
Asset: "File"
Assets: "Files"

View file

@ -472,15 +472,6 @@ Rails.application.routes.draw do
post 'avatar_signature' => 'users/registrations#signature'
get 'users/auth_token_sign_in' => 'users/sessions#auth_token_create'
end
namespace :api, defaults: { format: 'json' } do
get 'status', to: 'api#status'
post 'auth/token', to: 'api#authenticate'
scope '20170715', module: 'v20170715' do
get 'tasks/tree', to: 'core_api#tasks_tree'
get 'tasks/:task_id/samples', to: 'core_api#task_samples'
end
end
end
constraints WopiSubdomain do

View file

@ -20,7 +20,7 @@
"lint": "eslint ."
},
"devDependencies": {
"eslint": "^3.19.0",
"eslint": "^3.7.1",
"eslint-config-google": "^0.5.0",
"eslint-plugin-react": "^7.1.0",
"webpack-dev-server": "^2.5.1"

View file

@ -1,65 +0,0 @@
require 'rails_helper'
describe Api::ApiController, type: :controller do
describe 'GET #status' do
before do
get :status
end
it 'Returns HTTP success' do
expect(response).to be_success
expect(response).to have_http_status(200)
end
it 'Response with correct JSON status structure' do
hash_body = nil
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match(
'message' => I18n.t('api.core.status_ok'),
'versions' => [{ 'version' => '20170715',
'baseUrl' => '/api/20170715/' }]
)
end
end
describe 'Post #authenticate' do
let(:user) { create(:user) }
context 'When valid request' do
before do
post :authenticate, params: { email: user.email,
password: user.password,
grant_type: 'password' }
end
it 'Returns HTTP success' do
expect(response).to have_http_status(200)
end
it 'Returns valid JWT token' do
token = nil
expect { token = json['access_token'] }.not_to raise_exception
user_id = nil
expect { user_id = decode_token(token) }.not_to raise_exception
expect(user_id).to eq(user.id)
end
end
context 'When invalid password in request' do
it 'Returns HTTP error' do
post :authenticate, params: { email: user.email,
password: 'wrong_password',
grant_type: 'password' }
expect(response).to have_http_status(400)
end
end
context 'When no grant_type in request' do
it 'Returns HTTP error' do
post :authenticate, params: { email: user.email,
password: user.password }
expect(response).to have_http_status(400)
end
end
end
end

View file

@ -1,129 +0,0 @@
require 'rails_helper'
describe Api::V20170715::CoreApiController, type: :controller do
let(:user) { create(:user) }
let(:task) { create(:my_module) }
let(:sample1) { create(:sample) }
let(:sample2) { create(:sample) }
let(:sample3) { create(:sample) }
before do
task.samples << [sample1, sample2, sample3]
UserProject.create!(user: user, project: task.experiment.project, role: 0)
end
describe 'GET #task_samples' do
context 'When valid request' do
before do
request.headers['HTTP_ACCEPT'] = 'application/json'
request.headers['Authorization'] = 'Bearer ' + generate_token(user.id)
get :task_samples, params: { task_id: task.id }
end
it 'Returns HTTP success' do
expect(response).to have_http_status(200)
end
it 'Returns JSON body containing expected Samples' do
hash_body = nil
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match(
[{ 'sample_id' => sample1.id.to_s, 'name' => sample1.name },
{ 'sample_id' => sample2.id.to_s, 'name' => sample2.name },
{ 'sample_id' => sample3.id.to_s, 'name' => sample3.name }]
)
end
end
context 'When invalid request' do
context 'When invalid token' do
before do
request.headers['HTTP_ACCEPT'] = 'application/json'
request.headers['Authorization'] = 'Bearer WroNgToken'
get :task_samples, params: { task_id: task.id }
end
it 'Returns HTTP access denied' do
expect(response).to have_http_status(403)
end
end
context 'When valid token, but invalid request' do
before do
request.headers['HTTP_ACCEPT'] = 'application/json'
request.headers['Authorization'] = 'Bearer ' + generate_token(user.id)
end
it 'Returns HTTP not found' do
get :task_samples, params: { task_id: 1000 }
expect(response).to have_http_status(404)
expect(json).to match({})
end
it 'Returns HTTP access denied' do
UserProject.where(user: user, project: task.experiment.project)
.first
.destroy!
get :task_samples, params: { task_id: task.id }
expect(response).to have_http_status(403)
expect(json).to match({})
end
end
end
end
describe 'GET #tasks_tree' do
context 'When valid request' do
before do
request.headers['HTTP_ACCEPT'] = 'application/json'
request.headers['Authorization'] = 'Bearer ' + generate_token(user.id)
get :tasks_tree
end
it 'Returns HTTP success' do
expect(response).to have_http_status(200)
end
it 'Returns JSON body containing expected Task tree' do
team = user.teams.first
experiment = task.experiment
project = experiment.project
hash_body = nil
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match(
['team_id' => team.id.to_s, 'name' => team.name,
'description' => team.description,
'projects' => [{
'project_id' => project.id.to_s, 'name' => project.name,
'visibility' => project.visibility,
'archived' => project.archived,
'experiments' => [{
'experiment_id' => experiment.id.to_s,
'name' => experiment.name,
'description' => experiment.description,
'archived' => experiment.archived,
'tasks' => [{
'task_id' => task.id.to_s, 'name' => task.name,
'description' => task.description,
'archived' => task.archived
}]
}]
}]]
)
end
end
context 'When invalid request' do
context 'When invalid token' do
before do
request.headers['HTTP_ACCEPT'] = 'application/json'
request.headers['Authorization'] = 'Bearer WroNgToken'
get :tasks_tree
end
it 'Returns HTTP access denied' do
expect(response).to have_http_status(403)
end
end
end
end
end

View file

@ -5,8 +5,5 @@ FactoryGirl.define do
email 'admin_test@scinote.net'
password 'asdf1243'
password_confirmation 'asdf1243'
after(:create) do |user|
user.teams << (Team.first || FactoryGirl.create(:team))
end
end
end

View file

@ -9,8 +9,6 @@ abort('The Rails environment is running in production mode!') if Rails.env.produ
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
@ -80,8 +78,6 @@ RSpec.configure do |config|
# includes FactoryGirl in rspec
config.include FactoryGirl::Syntax::Methods
config.include Devise::Test::ControllerHelpers, type: :controller
config.include ApiHelper, type: :controller
end
# config shoulda matchers to work with rspec

View file

@ -1,17 +0,0 @@
module ApiHelper
def generate_token(user_id)
Api::CoreJwt.encode(user_id: user_id)
end
def generate_expired_token(user_id)
Api::CoreJwt.encode({ user_id: user_id }, (Time.now.to_i - 300))
end
def decode_token(token)
Api::CoreJwt.decode(token)['user_id'].to_i
end
def json
JSON.parse(response.body)
end
end