mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-02-05 22:44:22 +08:00
Merge pull request #766 from biosistemika/revert-763-ok_SCI_1507
Revert "Add core API using JWT authentication and tests for it [SCI-1507]"
This commit is contained in:
commit
5115890d07
16 changed files with 1 additions and 447 deletions
3
Gemfile
3
Gemfile
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue