Merge branch 'develop' into release-1-17-8

This commit is contained in:
Oleksii Kriuchykhin 2019-12-11 10:55:00 +01:00
commit 1aa334fdaa
25 changed files with 252 additions and 102 deletions

View file

@ -301,7 +301,7 @@ GEM
js_cookie_rails (2.2.0)
railties (>= 3.1)
json (1.8.6)
json-jwt (1.10.2)
json-jwt (1.11.0)
activesupport (>= 4.2)
aes_key_wrap
bindata
@ -346,7 +346,7 @@ GEM
mini_magick (4.9.5)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.11.3)
minitest (5.13.0)
momentjs-rails (2.17.1)
railties (>= 3.1)
msgpack (1.3.1)
@ -590,7 +590,7 @@ GEM
wkhtmltopdf-heroku (2.12.5.0)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.1.10)
zeitwerk (2.2.1)
PLATFORMS
ruby

View file

@ -80,7 +80,7 @@ module Api
end
# Default token implementation
unless iss == Api.configuration.core_api_token_iss
unless iss == Rails.configuration.x.core_api_token_iss
raise JWT::InvalidPayload, I18n.t('api.core.wrong_iss')
end
payload = CoreJwt.decode(token)

View file

@ -28,7 +28,11 @@ class RepositoriesController < ApplicationController
render 'repositories/index'
end
def show; end
def show
@display_edit_button = can_create_repository_rows?(@repository)
@display_delete_button = can_delete_repository_rows?(@repository)
@display_duplicate_button = can_create_repository_rows?(@repository)
end
def create_modal
@repository = Repository.new

View file

@ -12,8 +12,9 @@ class RepositoryRowsController < ApplicationController
copy_records
available_rows)
before_action :check_create_permissions, only: :create
before_action :check_delete_permissions, only: :delete_records
before_action :check_manage_permissions,
only: %i(edit update delete_records copy_records)
only: %i(edit update copy_records)
def index
@draw = params[:draw].to_i
@ -372,6 +373,10 @@ class RepositoryRowsController < ApplicationController
render_403 unless can_manage_repository_rows?(@repository)
end
def check_delete_permissions
render_403 unless can_delete_repository_rows?(@repository)
end
def record_params
params.permit(:repository_row_name).to_h
end

View file

@ -1,4 +1,8 @@
# frozen_string_literal: true
class Users::SessionsController < Devise::SessionsController
layout :session_layout
# before_filter :configure_sign_in_params, only: [:create]
after_action :after_sign_in, only: :create
@ -8,6 +12,7 @@ class Users::SessionsController < Devise::SessionsController
# GET /resource/sign_in
def new
@simple_sign_in = params[:simple_sign_in] == 'true'
# If user was redirected here from OAuth's authorize/new page (Doorkeeper
# endpoint for authorizing an OAuth client), 3rd party sign-in buttons
# (e.g. LinkedIn) should be hidden. See config/initializers/devise.rb.
@ -76,4 +81,14 @@ class Users::SessionsController < Devise::SessionsController
def configure_sign_in_params
devise_parameter_sanitizer.for(:sign_in) << :attribute
end
private
def session_layout
if @simple_sign_in
'sign_in_halt'
else
'layouts/main'
end
end
end

View file

@ -0,0 +1,19 @@
module Users
module Settings
module Account
class ConnectedAccountsController < ApplicationController
layout 'fluid'
def index
@linked_accounts = current_user.user_identities.pluck(:provider)
end
def destroy
current_user.user_identities.where(provider: params.require(:provider)).take&.destroy!
@linked_accounts = current_user.user_identities.pluck(:provider)
render :index
end
end
end
end
end

View file

@ -18,7 +18,7 @@ module LeftMenuBarHelper
end
def settings_are_selected?
controller_name.in? %(registrations preferences addons teams)
controller_name.in? %(registrations preferences addons teams connected_accounts)
end
def activities_are_selected?

View file

@ -2,7 +2,8 @@ module UserSettingsHelper
def on_settings_account_page?
controller_name == 'registrations' && action_name == 'edit' ||
controller_name == 'preferences' && action_name == 'index' ||
controller_name == 'addons' && action_name == 'index'
controller_name == 'addons' && action_name == 'index' ||
controller_name == 'connected_accounts'
end
def on_settings_account_profile_page?
@ -21,4 +22,8 @@ module UserSettingsHelper
controller_name.in?(%w(teams audits)) &&
action_name.in?(%w(index new create show audits_index))
end
def on_settings_account_connected_accounts_page?
controller_name == 'connected_accounts'
end
end

View file

@ -486,7 +486,7 @@ class User < ApplicationRecord
includes(:user_identities)
.where(
'user_identities.provider=? AND user_identities.uid=?',
Api.configuration.azure_ad_apps[token_payload[:aud]][:provider],
Rails.configuration.x.azure_ad_apps[token_payload[:aud]][:provider],
token_payload[:sub]
)
.references(:user_identities)

View file

@ -30,6 +30,14 @@ Canaid::Permissions.register_for(Repository) do
can_create_repository_rows?(user, repository)
end
can :update_repository_rows do |user, repository|
can_manage_repository_rows?(user, repository)
end
can :delete_repository_rows do |user, repository|
can_manage_repository_rows?(user, repository)
end
# repository: create field
can :create_repository_columns do |user, repository|
can_create_repository_rows?(user, repository) unless repository.shared_with?(user.current_team)

View file

@ -1,31 +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
attr_accessor :azure_ad_apps
attr_accessor :core_api_v1_enabled
attr_accessor :core_api_rate_limit
def initialize
@core_api_sign_alg = 'HS256'
@core_api_token_ttl = 30.minutes
@core_api_token_iss = 'SciNote'
@azure_ad_apps = {}
@core_api_v1_enabled = false
@core_api_rate_limit = 1000
end
end
end

View file

@ -9,7 +9,7 @@ module Api
def self.fetch_rsa_key(k_id, app_id)
cache_key = "api_azure_ad_rsa_key_#{k_id}"
Rails.cache.fetch(cache_key, expires_in: KEYS_CACHING_PERIOD) do
conf_url = Api.configuration.azure_ad_apps[app_id][:conf_url]
conf_url = Rails.configuration.x.azure_ad_apps[app_id][:conf_url]
keys_url = JSON.parse(Net::HTTP.get(URI(conf_url)))['jwks_uri']
data = JSON.parse(Net::HTTP.get(URI.parse(keys_url)))
verif_key = data['keys'].find { |key| key['kid'] == k_id }
@ -35,7 +35,7 @@ module Api
# Now search for matching app variables in configuration
app_id = unverified_token[0]['aud']
app_config = Api.configuration.azure_ad_apps[app_id]
app_config = Rails.configuration.x.azure_ad_apps[app_id]
unless app_config
raise JWT::VerificationError,
'Azure AD: No application configured with such ID'

View file

@ -7,15 +7,15 @@ module Api
if expires_at
payload[:exp] = expires_at
else
payload[:exp] = Api.configuration.core_api_token_ttl.from_now.to_i
payload[:exp] = Rails.configuration.x.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)
payload[:iss] = Rails.configuration.x.core_api_token_iss
JWT.encode(payload, KEY_SECRET, Rails.configuration.x.core_api_sign_alg)
end
def self.decode(token)
HashWithIndifferentAccess.new(
JWT.decode(token, KEY_SECRET, Api.configuration.core_api_sign_alg)[0]
JWT.decode(token, KEY_SECRET, Rails.configuration.x.core_api_sign_alg)[0]
)
end

View file

@ -27,11 +27,13 @@
<%= image_tag('/images/scinote_icon.jpg', id: 'logo') %>
</span>
</div>
<div class="pull-right sign-in-halt-logout-button">
<%= link_to main_app.destroy_user_session_path, class: 'btn btn-default', method: :delete do %>
<%= t('nav.user.logout') %>
<% end %>
</div>
<% if user_signed_in? %>
<div class="pull-right sign-in-halt-logout-button">
<%= link_to main_app.destroy_user_session_path, class: 'btn btn-default', method: :delete do %>
<%= t('nav.user.logout') %>
<% end %>
</div>
<% end %>
</div>
</nav>
<% if flash[:error]%>

View file

@ -128,21 +128,29 @@
<% end %>
<% if can_manage_repository_rows?(@repository) %>
<button type="button" class="btn btn-default editAdd" id="editRepositoryRecord" onclick="onClickEdit()" disabled>
<span class="fas fa-pencil-alt"></span>
<span class="hidden-xs-custom"><%= t("repositories.edit_record") %></span>
</button>
<button type="button" class="btn btn-default"
id="deleteRepositoryRecordsButton" onclick="onClickDelete()" disabled>
<span class="fas fa-trash"></span>
<span class="hidden-xs-custom"><%= t'repositories.delete_record' %></span>
<%= submit_tag I18n.t('repositories.delete_record'), :class => "hidden
delete_repository_records_submit" %>
</button>
<button type="button" class="btn btn-default copyRow" id="copyRepositoryRecords" onclick="onClickCopyRepositoryRecords()" disabled>
<span class="fas fa-copy"></span>
<span class="hidden-xs-custom"><%= t("repositories.copy_record") %></span>
</button>
<%if @display_edit_button %>
<button type="button" class="btn btn-default editAdd" id="editRepositoryRecord" onclick="onClickEdit()" disabled>
<span class="fas fa-pencil-alt"></span>
<span class="hidden-xs-custom"><%= t("repositories.edit_record") %></span>
</button>
<% end %>
<%if @display_delete_button %>
<button type="button" class="btn btn-default"
id="deleteRepositoryRecordsButton" onclick="onClickDelete()" disabled>
<span class="fas fa-trash"></span>
<span class="hidden-xs-custom"><%= t'repositories.delete_record' %></span>
<%= submit_tag I18n.t('repositories.delete_record'), :class => "hidden delete_repository_records_submit" %>
</button>
<% end %>
<%if @display_duplicate_button %>
<button type="button" class="btn btn-default copyRow" id="copyRepositoryRecords" onclick="onClickCopyRepositoryRecords()" disabled>
<span class="fas fa-copy"></span>
<span class="hidden-xs-custom"><%= t("repositories.copy_record") %></span>
</button>
<%end%>
<% elsif @repository.shared_with?(current_team) %>
<p class="view-only-label"><%= t('repositories.index.view_only_permission_label') %></p>
<% end %>

View file

@ -18,17 +18,19 @@
<%= f.password_field :password, autocomplete: "off", class: "form-control", placeholder: t("devise.sessions.new.password_placeholder") %>
</div>
<% if devise_mapping.rememberable? -%>
<% if devise_mapping.rememberable? && !@simple_sign_in %>
<div class="field" style="margin-top: 10px;">
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
</div>
<% end -%>
<% end %>
<%= hidden_field_tag(:simple_sign_in, @simple_sign_in) %>
<div class="actions" style="margin-top: 10px; margin-bottom: 10px;">
<%= f.submit t("devise.sessions.new.submit"), class: "btn btn-primary" %>
</div>
<% end %>
<%= render "users/shared/links" %>
<%= render "users/shared/links" unless @simple_sign_in %>
</div>

View file

@ -56,6 +56,20 @@
<% end %>
</span>
</li>
<!-- Connected accounts -->
<li class="<%= 'active' if on_settings_account_connected_accounts_page? %>" >
<span class="tree-link line-wrap first-indent">
<% if on_settings_account_connected_accounts_page? %>
<span title="<%= t("users.settings.sidebar.account_nav.connected_accounts") %>">
<%= t("users.settings.sidebar.account_nav.connected_accounts") %>
</span>
<% else %>
<%= link_to t("users.settings.sidebar.account_nav.connected_accounts"),
connected_accounts_path,
data: { 'no-turbolink' => 'true' } %>
<% end %>
</span>
</li>
</ul>
</li>

View file

@ -0,0 +1,24 @@
<div class="panel panel-default">
<div class="panel-body">
<div class="col-xs-8 col-sm-9 col-md-9 col-lg-9">
<strong><%= t('users.settings.account.connected_accounts.azure_ad.title') %></strong> <br>
<p><%= t('users.settings.account.connected_accounts.azure_ad.connect_hint') %></p>
</div>
<div class="pull-right">
<div>
<strong>
<%= t('users.settings.account.connected_accounts.azure_ad.connected') %>
<span class="fas fa-check" aria-hidden="true"></span>
</strong>
</div>
<div>
<%= link_to t('users.settings.account.connected_accounts.azure_ad.unlink_button'),
'#unlinkAzureADModal',
class: 'btn btn-danger',
data: { toggle: 'modal'} %>
</div>
</div>
</div>
</div>
<%= render partial: 'users/settings/account/connected_accounts/unlink_modals/azure_ad_modal', locals: { provider: provider } %>

View file

@ -0,0 +1,31 @@
<% provide(:head_title, t("users.settings.account.connected_accounts.head_title")) %>
<%= render partial: "users/settings/sidebar.html.erb" %>
<div class="tab-content">
<div class="tab-pane tab-pane-settings active" role="tabpanel">
<div class="row">
<div class="col-xs-12 col-sm-12">
<h4><%= t('users.settings.account.connected_accounts.title') %></h4>
<% if @linked_accounts.any? %>
<% @linked_accounts.each do |provider| %>
<% if Rails.configuration.x.azure_ad_apps.find { |_,value| value[:provider] == provider } %>
<% if lookup_context.exists?(provider, 'users/settings/account/connected_accounts', true) %>
<%= render partial: provider %>
<% else %>
<%= render partial: 'azure_ad', locals: { provider: provider } %>
<% end %>
<% end %>
<% end %>
<% else %>
<h5>
<i><%= t('users.settings.account.connected_accounts.not_connected') %></i>
</h5>
<% end %>
</div>
</div>
</div>
<div class="tab-pane tab-pane-settings" role="tabpanel"></div>
</div>

View file

@ -0,0 +1,23 @@
<div class="modal fade" id="unlinkAzureADModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="<%= t('general.close') %>">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" >
<%= t('users.settings.account.connected_accounts.azure_ad.unlink_modal.title') %>
</h4>
</div>
<div class="modal-body">
<p><%= t('users.settings.account.connected_accounts.azure_ad.unlink_modal.description_1') %></p>
</div>
<div class="modal-footer">
<%= form_tag(unlink_connected_account_path, method: :delete) do %>
<%= hidden_field_tag :provider, provider %>
<%= submit_tag t('users.settings.account.connected_accounts.azure_ad.unlink_modal.submit_button'), class: 'btn btn-danger' %>
<% end %>
</div>
</div>
</div>
</div>

View file

@ -1,36 +1,13 @@
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'].to_i.seconds
end
if ENV['CORE_API_TOKEN_ISS']
config.core_api_token_iss = ENV['CORE_API_TOKEN_ISS']
end
# frozen_string_literal: true
config.core_api_rate_limit =
ENV['CORE_API_RATE_LIMIT'] ? ENV['CORE_API_RATE_LIMIT'].to_i : 1000
Rails.application.configure do
config.x.core_api_sign_alg = ENV['CORE_API_SIGN_ALG'] || 'HS256'
config.core_api_v1_enabled = true if ENV['CORE_API_V1_ENABLED']
config.x.core_api_token_ttl = ENV['CORE_API_TOKEN_TTL'] ? ENV['CORE_API_TOKEN_TTL'].to_i.seconds : 30.minutes
vars = ENV.select { |name, _| name =~ /^[[:alnum:]]*_AZURE_AD_APP_ID/ }
vars.each do |name, value|
app_name = name.sub('_AZURE_AD_APP_ID', '')
config.azure_ad_apps[value] = {}
config.x.core_api_token_iss = ENV['CORE_API_TOKEN_ISS'] || 'SciNote'
iss = ENV["#{app_name}_AZURE_AD_ISS"]
raise StandardError, "No ISS for #{app_name} Azure app" unless iss
config.azure_ad_apps[value][:iss] = iss
config.x.core_api_rate_limit = ENV['CORE_API_RATE_LIMIT'] ? ENV['CORE_API_RATE_LIMIT'].to_i : 1000
conf_url = ENV["#{app_name}_AZURE_AD_CONF_URL"]
raise StandardError, "No CONF_URL for #{app_name} Azure app" unless conf_url
config.azure_ad_apps[value][:conf_url] = conf_url
provider = ENV["#{app_name}_AZURE_AD_PROVIDER_NAME"]
unless provider
raise StandardError, "No PROVIDER_NAME for #{app_name} Azure app"
end
config.azure_ad_apps[value][:provider] = provider
end
config.x.core_api_v1_enabled = ENV['CORE_API_V1_ENABLED'] || false
end

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
Rails.application.configure do
vars = ENV.select { |name, _| name =~ /^[[:alnum:]]*_AZURE_AD_APP_ID/ }
vars.each do |name, value|
app_name = name.sub('_AZURE_AD_APP_ID', '')
config.x.azure_ad_apps[value] = {}
iss = ENV["#{app_name}_AZURE_AD_ISS"]
raise StandardError, "No ISS for #{app_name} Azure app" unless iss
config.x.azure_ad_apps[value][:iss] = iss
conf_url = ENV["#{app_name}_AZURE_AD_CONF_URL"]
raise StandardError, "No CONF_URL for #{app_name} Azure app" unless conf_url
config.x.azure_ad_apps[value][:conf_url] = conf_url
provider = ENV["#{app_name}_AZURE_AD_PROVIDER_NAME"]
raise StandardError, "No PROVIDER_NAME for #{app_name} Azure app" unless provider
config.x.azure_ad_apps[value][:provider] = provider
end
end

View file

@ -2,10 +2,10 @@
return unless Rails.env.production?
return if Api.configuration.core_api_rate_limit.zero?
return if Rails.configuration.x.core_api_rate_limit.zero?
Rack::Attack.throttle('api requests by ip',
limit: Api.configuration.core_api_rate_limit,
limit: Rails.configuration.x.core_api_rate_limit,
period: 60) do |request|
request.ip if request.path.match?(%r{^\/api\/})
end

View file

@ -1463,6 +1463,7 @@ en:
profile: "Profile"
preferences: "Preferences"
addons: "Add-ons"
connected_accounts: "Connected Accounts"
account:
preferences:
head_title: "Settings | My preferences"
@ -1483,6 +1484,19 @@ en:
head_title: "Settings | Add-ons"
title: "Add-ons"
no_addons: "You have no SciNote Add-ons."
connected_accounts:
head_title: "Settings | Connected Accounts"
title: "Connected Accounts"
not_connected: "You have no Connected accounts"
azure_ad:
title: "Your Azure AD Account"
connect_hint: "Allows you to sign in with your Azure AD account."
connected: "Connected"
unlink_button: "Unlink"
unlink_modal:
title: "Unlink Azure AD account?"
description_1: "Are you sure you would like unlink Azure AD and SciNote accounts?"
submit_button: "Submit"
teams:
head_title: "Settings | Teams"
breadcrumbs:

View file

@ -62,6 +62,12 @@ Rails.application.routes.draw do
get 'users/settings/account/addons',
to: 'users/settings/account/addons#index',
as: 'addons'
get 'users/settings/account/connected_accounts',
to: 'users/settings/account/connected_accounts#index',
as: 'connected_accounts'
delete 'users/settings/account/connected_accounts',
to: 'users/settings/account/connected_accounts#destroy',
as: 'unlink_connected_account'
put 'users/settings/account/preferences',
to: 'users/settings/account/preferences#update',
as: 'update_preferences'
@ -620,7 +626,7 @@ Rails.application.routes.draw do
namespace :api, defaults: { format: 'json' } do
get 'health', to: 'api#health'
get 'status', to: 'api#status'
if Api.configuration.core_api_v1_enabled || Rails.env.development?
if Rails.configuration.x.core_api_v1_enabled
namespace :v1 do
resources :teams, only: %i(index show) do
resources :inventories,