diff --git a/app/controllers/api/v1/user_identities_controller.rb b/app/controllers/api/v1/user_identities_controller.rb new file mode 100644 index 000000000..8cada3af4 --- /dev/null +++ b/app/controllers/api/v1/user_identities_controller.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Api + module V1 + class UserIdentitiesController < BaseController + before_action :load_user + before_action :load_user_identity, only: %i(show update destroy) + + def index + identities = + @user.user_identities.page(params[:page]).per(params[:page_size]) + render json: identities, each_serializer: UserIdentitySerializer + end + + def create + identity = @user.user_identities.create!(user_identity_params) + render json: identity, + serializer: UserIdentitySerializer, + status: :created + end + + def show + render json: @identity, serializer: UserIdentitySerializer + end + + def update + @identity.attributes = update_user_identity_params + if @identity.changed? && @identity.save! + render json: @identity, serializer: UserIdentitySerializer + else + render body: nil + end + end + + def destroy + @identity.destroy! + render body: nil + end + + private + + def load_user + @user = current_user if current_user.id == params[:user_id].to_i + return render body: nil, status: :forbidden unless @user + end + + def load_user_identity + @identity = @user.user_identities.find(params[:id].to_i) + end + + def user_identity_params + unless params.require(:data).require(:type) == 'user_identities' + raise ActionController::BadRequest, + 'Wrong object type within parameters' + end + params.require(:data).require(:attributes) + params.permit(data: { attributes: %i(provider uid) })[:data] + end + + def update_user_identity_params + unless params.require(:data).require(:id).to_i == params[:id].to_i + raise ActionController::BadRequest, + 'Object ID mismatch in URL and request body' + end + user_identity_params + end + end + end +end diff --git a/app/models/user_identity.rb b/app/models/user_identity.rb index e0ae551e4..2d1f46a23 100644 --- a/app/models/user_identity.rb +++ b/app/models/user_identity.rb @@ -1,5 +1,5 @@ class UserIdentity < ActiveRecord::Base belongs_to :user - validates :provider, uniqueness: { scope: :user_id } - validates :uid, uniqueness: { scope: :provider } + validates :provider, presence: true, uniqueness: { scope: :user_id } + validates :uid, presence: true, uniqueness: { scope: :provider } end diff --git a/app/serializers/api/v1/user_identity_serializer.rb b/app/serializers/api/v1/user_identity_serializer.rb new file mode 100644 index 000000000..137596bd4 --- /dev/null +++ b/app/serializers/api/v1/user_identity_serializer.rb @@ -0,0 +1,7 @@ +module Api + module V1 + class UserIdentitySerializer < ActiveModel::Serializer + attributes :provider, :uid + end + end +end diff --git a/app/services/api.rb b/app/services/api.rb index 589cc0275..a06f328be 100644 --- a/app/services/api.rb +++ b/app/services/api.rb @@ -16,12 +16,14 @@ module Api attr_accessor :core_api_token_ttl attr_accessor :core_api_token_iss attr_accessor :azure_ad_apps + attr_accessor :core_api_v1_preview def initialize @core_api_sign_alg = 'HS256' @core_api_token_ttl = 30.minutes @core_api_token_iss = 'SciNote' @azure_ad_apps = {} + @core_api_v1_preview = false end end end diff --git a/config/initializers/api.rb b/config/initializers/api.rb index 4ab3e3898..cad116bbf 100644 --- a/config/initializers/api.rb +++ b/config/initializers/api.rb @@ -9,6 +9,8 @@ Api.configure do |config| config.core_api_token_iss = ENV['CORE_API_TOKEN_ISS'] end + config.core_api_v1_preview = true if ENV['CORE_API_V1_PREVIEW'] + vars = ENV.select { |name, _| name =~ /^[[:alnum:]]*_AZURE_AD_APP_ID/ } vars.each do |name, value| app_name = name.sub('_AZURE_AD_APP_ID', '') diff --git a/config/routes.rb b/config/routes.rb index 6896733d3..a3a1d412f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -540,14 +540,17 @@ Rails.application.routes.draw do get 'health', to: 'api#health' get 'status', to: 'api#status' post 'auth/token', to: 'api#authenticate' - namespace :v1 do - resources :teams, only: %i(index show) do - resources :inventories, only: %i(index show) do - get 'columns', to: 'inventory_columns#index' - get 'items', to: 'inventory_items#index' + if Api.configuration.core_api_v1_preview + namespace :v1 do + resources :teams, only: %i(index show) do + resources :inventories, only: %i(index show) do + get 'columns', to: 'inventory_columns#index' + get 'items', to: 'inventory_items#index' + end + end + resources :users, only: %i(show) do + resources :user_identities, only: %i(index create show update destroy) end - end - resources :users, only: %i(show) do end end end diff --git a/spec/factories/user_identeties.rb b/spec/factories/user_identeties.rb new file mode 100644 index 000000000..c9035a241 --- /dev/null +++ b/spec/factories/user_identeties.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :user_identity do + uid Faker::Crypto.unique.sha1 + provider Faker::App.unique.name + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 79e319489..1f74b4ae3 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -5,6 +5,9 @@ require 'database_cleaner' require 'devise' require_relative 'support/controller_macros' ENV['RAILS_ENV'] = 'test' + +ENV['CORE_API_V1_PREVIEW'] = 'true' + require File.expand_path('../../config/environment', __FILE__) # Prevent database truncation if the environment is production abort('The Rails environment is running in production mode!') if Rails.env.production? diff --git a/spec/requests/api/v1/users_controller_spec.rb b/spec/requests/api/v1/users_controller_spec.rb index d477384f2..73e7dc976 100644 --- a/spec/requests/api/v1/users_controller_spec.rb +++ b/spec/requests/api/v1/users_controller_spec.rb @@ -30,7 +30,7 @@ RSpec.describe 'Api::V1::UsersController', type: :request do ) end - it 'When invalid request, requested user in not member of the same teams' do + it 'When invalid request, requested user is not member of the same teams' do hash_body = nil get api_v1_user_path(id: @user3.id), headers: @valid_headers expect(response).to have_http_status(403) diff --git a/spec/requests/api/v1/users_identeties_controller_spec.rb b/spec/requests/api/v1/users_identeties_controller_spec.rb new file mode 100644 index 000000000..3a927f5a2 --- /dev/null +++ b/spec/requests/api/v1/users_identeties_controller_spec.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Api::V1::UsersIdentitiesController', type: :request do + before :all do + @user1 = create(:user, email: Faker::Internet.unique.email) + @user2 = create(:user, email: Faker::Internet.unique.email) + create(:user_identity, user: @user1, + uid: Faker::Crypto.unique.sha1, provider: Faker::App.unique.name) + @valid_headers = + { 'Authorization': 'Bearer ' + generate_token(@user1.id) } + end + + describe 'GET user_identities, #index' do + it 'When valid request, requested identities for current user' do + hash_body = nil + get api_v1_user_user_identities_path(user_id: @user1.id), + headers: @valid_headers + expect { hash_body = json }.not_to raise_exception + expect(hash_body[:data]).to match( + ActiveModelSerializers::SerializableResource + .new(@user1.user_identities, + each_serializer: Api::V1::UserIdentitySerializer) + .as_json[:data] + ) + end + + it 'When invalid request, requested user is not signed in user' do + hash_body = nil + get api_v1_user_user_identities_path(user_id: @user2.id), + headers: @valid_headers + expect(response).to have_http_status(403) + expect(hash_body).to match(nil) + end + + it 'When invalid request, non existing user' do + hash_body = nil + get api_v1_user_user_identities_path(user_id: 123), + headers: @valid_headers + expect(response).to have_http_status(403) + expect(hash_body).to match(nil) + end + end + + describe 'POST user_identities, #create' do + it 'When valid request, create new identity for current user' do + hash_body = nil + post api_v1_user_user_identities_path(user_id: @user1.id), + params: { + data: { type: 'user_identities', + attributes: { uid: Faker::Crypto.unique.sha1, + provider: Faker::App.unique.name } } + }, + headers: @valid_headers + expect { hash_body = json }.not_to raise_exception + expect(hash_body[:data]).to match( + ActiveModelSerializers::SerializableResource + .new(@user1.user_identities.order(:id).last, + serializer: Api::V1::UserIdentitySerializer) + .as_json[:data] + ) + end + end + + describe 'GET user_identity, #show' do + it 'When valid request, requested specific identity for current user' do + hash_body = nil + get api_v1_user_user_identity_path( + user_id: @user1.id, id: @user1.user_identities.order(:id).last + ), headers: @valid_headers + expect { hash_body = json }.not_to raise_exception + expect(hash_body[:data]).to match( + ActiveModelSerializers::SerializableResource + .new(@user1.user_identities.order(:id).last, + serializer: Api::V1::UserIdentitySerializer) + .as_json[:data] + ) + end + end + + describe 'PUT user_identities, #update' do + it 'When valid request, update identity for current user' do + hash_body = nil + put api_v1_user_user_identity_path(user_id: @user1.id, + id: @user1.user_identities.order(:id).last), + params: { + data: { id: @user1.user_identities.order(:id).last.id, + type: 'user_identities', + attributes: { uid: Faker::Crypto.unique.sha1 } } + }, + headers: @valid_headers + expect { hash_body = json }.not_to raise_exception + expect(hash_body[:data]).to match( + ActiveModelSerializers::SerializableResource + .new(@user1.user_identities.order(:id).last, + serializer: Api::V1::UserIdentitySerializer) + .as_json[:data] + ) + end + end + + describe 'DELETE user_identity, #destroy' do + it 'When valid request, destroy specified identity for current user' do + identity = @user1.user_identities.order(:id).last + delete api_v1_user_user_identity_path(user_id: @user1.id, id: identity), + headers: @valid_headers + expect(response).to have_http_status(200) + expect(response.body).to eq('') + expect(UserIdentity.find_by_id(identity.id)).to eq(nil) + end + end +end