1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-28 09:24:24 +01:00

(feat) cache auth provider config

This commit is contained in:
Sylvain 2023-03-29 18:01:16 +02:00
parent 41ed9b93f5
commit 66f740104a
19 changed files with 258 additions and 130 deletions

View File

@ -1,5 +1,6 @@
# Ignore bundler config. # Ignore bundler config.
config/database.yml config/database.yml
config/auth_provider.yml
# Ignore database files. # Ignore database files.
postgresql postgresql

1
.gitignore vendored
View File

@ -26,6 +26,7 @@
# Ignore application configurations # Ignore application configurations
/config/application.yml /config/application.yml
/config/database.yml /config/database.yml
/config/auth_provider.yml
.env .env
*.DS_Store *.DS_Store

View File

@ -22,8 +22,10 @@
- Fill the holes in the logical sequence of invoices references with nil invoices - Fill the holes in the logical sequence of invoices references with nil invoices
- Updated the invoices chaining method with a more flexible model - Updated the invoices chaining method with a more flexible model
- Fix a bug: broken display after a plan category was deleted - Fix a bug: broken display after a plan category was deleted
- [TODO DEPLOY] `rails fablab:restore_order_number` THEN `rails fablab:fix_references`
- Fix a security issue: updated json5 to 2.2.2 to fix [CVE-2022-46175](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-46175) - Fix a security issue: updated json5 to 2.2.2 to fix [CVE-2022-46175](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-46175)
- [TODO DEPLOY] `\curl -sSL https://raw.githubusercontent.com/sleede/fab-manager/master/scripts/mount-auth-provider.sh | bash`
- [TODO DEPLOY] `rails fablab:auth:write_provider`
- [TODO DEPLOY] `rails fablab:restore_order_number` THEN `rails fablab:fix_references`
## v5.9.1 2023 March 22 ## v5.9.1 2023 March 22

View File

@ -2,13 +2,12 @@
# Devise controller for handling client sessions # Devise controller for handling client sessions
class SessionsController < Devise::SessionsController class SessionsController < Devise::SessionsController
def new def new
active_provider = AuthProvider.active active_provider = Rails.configuration.auth_provider
if active_provider.providable_type != DatabaseProvider.name if active_provider.providable_type == 'DatabaseProvider'
redirect_post "/users/auth/#{active_provider.strategy_name}", params: { authenticity_token: form_authenticity_token }
else
super super
else
redirect_post "/users/auth/#{active_provider.strategy_name}", params: { authenticity_token: form_authenticity_token }
end end
end end
end end

View File

@ -5,7 +5,7 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
require 'sso_logger' require 'sso_logger'
logger = SsoLogger.new logger = SsoLogger.new
active_provider = AuthProvider.active active_provider = Rails.configuration.auth_provider
define_method active_provider.strategy_name do define_method active_provider.strategy_name do
logger.info "[Users::OmniauthCallbacksController##{active_provider.strategy_name}] initiated" logger.info "[Users::OmniauthCallbacksController##{active_provider.strategy_name}] initiated"
if request.env['omniauth.params'].blank? if request.env['omniauth.params'].blank?
@ -18,7 +18,7 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
logger.debug 'trying to create a new user' logger.debug 'trying to create a new user'
# If the username is mapped, we just check its uniqueness as it would break the postgresql # If the username is mapped, we just check its uniqueness as it would break the postgresql
# unique constraint otherwise. If the name is not unique, another unique is generated # unique constraint otherwise. If the name is not unique, another unique is generated
if active_provider.sso_fields.include?('user.username') if active_provider.db.sso_fields.include?('user.username')
logger.debug 'the username was already in use, generating a new one' logger.debug 'the username was already in use, generating a new one'
@user.username = generate_unique_username(@user.username) @user.username = generate_unique_username(@user.username)
end end
@ -26,7 +26,7 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# unique random string, because: # unique random string, because:
# - if it is the same user, his email will be filled from the SSO when he merge his accounts # - if it is the same user, his email will be filled from the SSO when he merge his accounts
# - if it is not the same user, this will prevent the raise of PG::UniqueViolation # - if it is not the same user, this will prevent the raise of PG::UniqueViolation
if active_provider.sso_fields.include?('user.email') && email_exists?(@user.email) if active_provider.db.sso_fields.include?('user.email') && email_exists?(@user.email)
logger.debug 'the email was already in use, marking it as duplicate' logger.debug 'the email was already in use, marking it as duplicate'
old_mail = @user.email old_mail = @user.email
@user.email = "<#{old_mail}>#{Devise.friendly_token}-duplicate" @user.email = "<#{old_mail}>#{Devise.friendly_token}-duplicate"
@ -46,13 +46,13 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
@user.email = User.find(@user.id).email @user.email = User.find(@user.id).email
end end
end end
# For users imported from the SSO, we consider the SSO as a source of trust so the email is automatically validated
@user.confirmed_at = Time.current if active_provider.db.sso_fields.include?('user.email') && !email_exists?(@user.email)
# We BYPASS THE VALIDATION because, in case of a new user, we want to save him anyway, we'll ask him later to complete his profile (on first login). # We BYPASS THE VALIDATION because, in case of a new user, we want to save him anyway, we'll ask him later to complete his profile (on first login).
# In case of an existing user, we trust the SSO validation as we want the SSO to have authority on users management and policy. # In case of an existing user, we trust the SSO validation as we want the SSO to have authority on users management and policy.
logger.debug 'saving the user' logger.debug 'saving the user'
unless @user.save(validate: false) logger.error "unable to save the user, an error occurred : #{@user.errors.full_messages.join(', ')}" unless @user.save(validate: false)
logger.error "unable to save the user, an error occurred : #{@user.errors.full_messages.join(', ')}"
end
logger.debug 'signing-in the user and redirecting' logger.debug 'signing-in the user and redirecting'
sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated
@ -77,7 +77,6 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
raise e raise e
end end
end end
end end
private private

View File

@ -11,6 +11,10 @@ class AuthProvider < ApplicationRecord
def name def name
'DatabaseProvider::SimpleAuthProvider' 'DatabaseProvider::SimpleAuthProvider'
end end
def strategy_name
"database-#{name.downcase.parameterize}"
end
end end
PROVIDABLE_TYPES = %w[DatabaseProvider OAuth2Provider OpenIdConnectProvider].freeze PROVIDABLE_TYPES = %w[DatabaseProvider OAuth2Provider OpenIdConnectProvider].freeze

View File

@ -7,8 +7,8 @@ module SingleSignOnConcern
included do included do
# enable OmniAuth authentication only if needed # enable OmniAuth authentication only if needed
devise :omniauthable, omniauth_providers: [AuthProvider.active.strategy_name.to_sym] unless devise :omniauthable, omniauth_providers: [Rails.configuration.auth_provider.strategy_name.to_sym] unless
AuthProvider.active.providable_type == DatabaseProvider.name Rails.configuration.auth_provider.providable_type == 'DatabaseProvider'
## Retrieve the requested data in the User and user's Profile tables ## Retrieve the requested data in the User and user's Profile tables
## @param sso_mapping {String} must be of form 'user._field_' or 'profile._field_'. Eg. 'user.email' ## @param sso_mapping {String} must be of form 'user._field_' or 'profile._field_'. Eg. 'user.email'
@ -39,7 +39,7 @@ module SingleSignOnConcern
## link the current user to the given provider (omniauth attributes hash) ## link the current user to the given provider (omniauth attributes hash)
## and remove the auth_token to mark his account as "migrated" ## and remove the auth_token to mark his account as "migrated"
def link_with_omniauth_provider(auth) def link_with_omniauth_provider(auth)
active_provider = AuthProvider.active active_provider = Rails.configuration.auth_provider
raise SecurityError, 'The identity provider does not match the activated one' if active_provider.strategy_name != auth.provider raise SecurityError, 'The identity provider does not match the activated one' if active_provider.strategy_name != auth.provider
if User.where(provider: auth.provider, uid: auth.uid).size.positive? if User.where(provider: auth.provider, uid: auth.uid).size.positive?
@ -104,7 +104,7 @@ module SingleSignOnConcern
def from_omniauth(auth) def from_omniauth(auth)
logger = SsoLogger.new logger = SsoLogger.new
logger.debug "[User::from_omniauth] initiated with parameter #{auth}" logger.debug "[User::from_omniauth] initiated with parameter #{auth}"
active_provider = AuthProvider.active active_provider = Rails.configuration.auth_provider
raise SecurityError, 'The identity provider does not match the activated one' if active_provider.strategy_name != auth.provider raise SecurityError, 'The identity provider does not match the activated one' if active_provider.strategy_name != auth.provider
where(provider: auth.provider, uid: auth.uid).first_or_create.tap do |user| where(provider: auth.provider, uid: auth.uid).first_or_create.tap do |user|

View File

@ -16,20 +16,4 @@ class OpenIdConnectProvider < ApplicationRecord
validates :display, inclusion: { in: %w[page popup touch wap], allow_nil: true } validates :display, inclusion: { in: %w[page popup touch wap], allow_nil: true }
validates :prompt, inclusion: { in: %w[none login consent select_account], allow_nil: true } validates :prompt, inclusion: { in: %w[none login consent select_account], allow_nil: true }
validates :client_auth_method, inclusion: { in: %w[basic jwks] } validates :client_auth_method, inclusion: { in: %w[basic jwks] }
def scope
self[:scope]&.join(' ')
end
def config
OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client__') && n != 'profile_url' }.map do |n|
[n, send(n)]
end.push(['client_options', client_config]).to_h
end
def client_config
OpenIdConnectProvider.columns.map(&:name).filter { |n| n.start_with?('client__') }.to_h do |n|
[n.sub('client__', ''), send(n)]
end
end
end end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
json.partial! 'api/auth_providers/auth_provider', auth_provider: provider
# OAuth 2.0
if provider.providable_type == 'OAuth2Provider'
json.providable_attributes do
json.extract! provider.providable, :id, :base_url, :token_endpoint, :authorization_endpoint, :profile_url, :client_id, :client_secret,
:scopes
end
end
if provider.providable_type == 'OpenIdConnectProvider'
json.providable_attributes do
json.extract! provider.providable, :id, :issuer, :discovery, :client_auth_method, :scope, :response_type, :response_mode,
:display, :prompt, :send_scope_to_token_endpoint, :client__identifier, :client__secret, :client__authorization_endpoint,
:client__token_endpoint, :client__userinfo_endpoint, :client__jwks_uri, :client__end_session_endpoint, :profile_url,
:post_logout_redirect_uri, :uid_field, :client__redirect_uri, :client__scheme, :client__host, :client__port
end
end

View File

@ -76,6 +76,9 @@ class FabManager::Application < Rails::Application
# disable ANSI color escape codes in active_record if NO_COLOR is defined. # disable ANSI color escape codes in active_record if NO_COLOR is defined.
config.colorize_logging = ENV['NO_COLOR'] ? false : true config.colorize_logging = ENV['NO_COLOR'] ? false : true
require 'provider_config'
config.auth_provider = ProviderConfig.new
FabManager.activate_plugins! FabManager.activate_plugins!
config.action_view.sanitized_allowed_tags = %w[a acronym hr pre table b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p config.action_view.sanitized_allowed_tags = %w[a acronym hr pre table b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p

View File

@ -227,17 +227,23 @@ Devise.setup do |config|
# Add a new OmniAuth provider. Check the wiki for more information on setting # Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks. # up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
Rails.application.reloader.to_prepare do active_provider = Rails.configuration.auth_provider
active_provider = AuthProvider.active unless active_provider.nil?
if active_provider.providable_type == OAuth2Provider.name # noinspection RubyCaseWithoutElseBlockInspection
case active_provider.providable_type
when 'OAuth2Provider'
require_relative '../../lib/omni_auth/oauth2' require_relative '../../lib/omni_auth/oauth2'
config.omniauth OmniAuth::Strategies::SsoOauth2Provider.name.to_sym, config.omniauth active_provider.strategy_name.to_sym,
active_provider.providable.client_id, active_provider.providable.client_id,
active_provider.providable.client_secret active_provider.providable.client_secret,
elsif active_provider.providable_type == OpenIdConnectProvider.name strategy_class: OmniAuth::Strategies::SsoOauth2Provider
when 'OpenIdConnectProvider'
require_relative '../../lib/omni_auth/openid_connect' require_relative '../../lib/omni_auth/openid_connect'
config.omniauth OmniAuth::Strategies::SsoOpenidConnectProvider.name.to_sym, config.omniauth active_provider.strategy_name.to_sym,
active_provider.providable.config active_provider.oidc_config.merge(
strategy_class: OmniAuth::Strategies::SsoOpenidConnectProvider
)
end end
end end

View File

@ -4,7 +4,7 @@ require 'sidekiq_unique_jobs/web'
require 'sidekiq-scheduler/web' require 'sidekiq-scheduler/web'
Rails.application.routes.draw do Rails.application.routes.draw do
if AuthProvider.active.providable_type == DatabaseProvider.name if Rails.configuration.auth_provider.providable_type == 'DatabaseProvider'
# with local authentication we do not use omniAuth so we must differentiate the config # with local authentication we do not use omniAuth so we must differentiate the config
devise_for :users, controllers: { devise_for :users, controllers: {
registrations: 'registrations', sessions: 'sessions', confirmations: 'confirmations', passwords: 'passwords' registrations: 'registrations', sessions: 'sessions', confirmations: 'confirmations', passwords: 'passwords'

View File

@ -249,6 +249,9 @@ unless DatabaseProvider.count.positive?
provider.providable = db_provider provider.providable = db_provider
provider.status = 'active' provider.status = 'active'
provider.save provider.save
require 'provider_config'
ProviderConfig.write_active_provider
end end
end end

View File

@ -5,76 +5,74 @@ require 'jsonpath'
require 'sso_logger' require 'sso_logger'
require_relative '../data_mapping/mapper' require_relative '../data_mapping/mapper'
module OmniAuth::Strategies # Authentication strategy provided trough oAuth 2.0
# Authentication strategy provided trough oAuth 2.0 class OmniAuth::Strategies::SsoOauth2Provider < OmniAuth::Strategies::OAuth2
class SsoOauth2Provider < OmniAuth::Strategies::OAuth2 include OmniAuth::DataMapping::Mapper
include OmniAuth::DataMapping::Mapper
def self.active_provider def self.active_provider
active_provider = AuthProvider.active active_provider = Rails.configuration.auth_provider
if active_provider.providable_type != OAuth2Provider.name if active_provider.providable_type != 'OAuth2Provider'
raise "Trying to instantiate the wrong provider: Expected OAuth2Provider, received #{active_provider.providable_type}" raise "Trying to instantiate the wrong provider: Expected OAuth2Provider, received #{active_provider.providable_type}"
end
active_provider
end end
# Strategy name. active_provider
option :name, active_provider.strategy_name end
option :client_options, # Strategy name.
site: active_provider.providable.base_url, option :name, active_provider.strategy_name
authorize_url: active_provider.providable.authorization_endpoint,
token_url: active_provider.providable.token_endpoint
def authorize_params option :client_options,
super.tap do |params| site: active_provider.providable.base_url,
params[:scope] = OmniAuth::Strategies::SsoOauth2Provider.active_provider.providable.scopes authorize_url: active_provider.providable.authorization_endpoint,
end token_url: active_provider.providable.token_endpoint
end
def callback_url def authorize_params
url = Rails.application.config.action_controller.default_url_options super.tap do |params|
"#{url[:protocol]}://#{url[:host]}#{script_name}#{callback_path}" params[:scope] = OmniAuth::Strategies::SsoOauth2Provider.active_provider.providable.scopes
end
uid { parsed_info[:'user.uid'] }
info do
{
mapping: parsed_info
}
end
extra do
{
raw_info: raw_info
}
end
# retrieve data from various url, querying each only once
def raw_info
logger = SsoLogger.new
@raw_info ||= {}
logger.debug "[raw_info] @raw_infos = #{@raw_info&.to_json}"
unless @raw_info.size.positive?
OmniAuth::Strategies::SsoOauth2Provider.active_provider.auth_provider_mappings.each do |mapping|
logger.debug "mapping = #{mapping&.to_json}"
next if @raw_info.key?(mapping.api_endpoint.to_sym)
logger.debug "api_endpoint = #{mapping.api_endpoint.to_sym}"
logger.debug "access_token = #{access_token&.to_json}"
logger.debug "token get = #{access_token.get(mapping.api_endpoint)}"
logger.debug "parsed = #{access_token.get(mapping.api_endpoint).parsed}"
@raw_info[mapping.api_endpoint.to_sym] = access_token.get(mapping.api_endpoint).parsed
end
end
@raw_info
end
def parsed_info
mapped_info(OmniAuth::Strategies::SsoOauth2Provider.active_provider.auth_provider_mappings, raw_info)
end end
end end
def callback_url
url = Rails.application.config.action_controller.default_url_options
"#{url[:protocol]}://#{url[:host]}#{script_name}#{callback_path}"
end
uid { parsed_info[:'user.uid'] }
info do
{
mapping: parsed_info
}
end
extra do
{
raw_info: raw_info
}
end
# retrieve data from various url, querying each only once
def raw_info
logger = SsoLogger.new
@raw_info ||= {}
logger.debug "[raw_info] @raw_infos = #{@raw_info&.to_json}"
unless @raw_info.size.positive?
OmniAuth::Strategies::SsoOauth2Provider.active_provider.auth_provider_mappings.each do |mapping|
logger.debug "mapping = #{mapping&.to_json}"
next if @raw_info.key?(mapping.api_endpoint.to_sym)
logger.debug "api_endpoint = #{mapping.api_endpoint.to_sym}"
logger.debug "access_token = #{access_token&.to_json}"
logger.debug "token get = #{access_token.get(mapping.api_endpoint)}"
logger.debug "parsed = #{access_token.get(mapping.api_endpoint).parsed}"
@raw_info[mapping.api_endpoint.to_sym] = access_token.get(mapping.api_endpoint).parsed
end
end
@raw_info
end
def parsed_info
mapped_info(OmniAuth::Strategies::SsoOauth2Provider.active_provider.auth_provider_mappings, raw_info)
end
end end

View File

@ -3,34 +3,32 @@
require 'omniauth_openid_connect' require 'omniauth_openid_connect'
require_relative '../data_mapping/mapper' require_relative '../data_mapping/mapper'
module OmniAuth::Strategies # Authentication strategy provided trough OpenID Connect
# Authentication strategy provided trough OpenID Connect class OmniAuth::Strategies::SsoOpenidConnectProvider < OmniAuth::Strategies::OpenIDConnect
class SsoOpenidConnectProvider < OmniAuth::Strategies::OpenIDConnect include OmniAuth::DataMapping::Mapper
include OmniAuth::DataMapping::Mapper
def self.active_provider def self.active_provider
active_provider = AuthProvider.active active_provider = Rails.configuration.auth_provider
if active_provider.providable_type != OpenIdConnectProvider.name if active_provider.providable_type != 'OpenIdConnectProvider'
raise "Trying to instantiate the wrong provider: Expected OpenIdConnectProvider, received #{active_provider.providable_type}" raise "Trying to instantiate the wrong provider: Expected OpenIdConnectProvider, received #{active_provider.providable_type}"
end
active_provider
end end
# Strategy name. active_provider
option :name, active_provider.strategy_name end
info do # Strategy name.
{ option :name, active_provider.strategy_name
mapping: parsed_info
}
end
def parsed_info info do
mapped_info( {
OmniAuth::Strategies::SsoOpenidConnectProvider.active_provider.auth_provider_mappings, mapping: parsed_info
user_info: user_info.raw_attributes }
) end
end
def parsed_info
mapped_info(
OmniAuth::Strategies::SsoOpenidConnectProvider.active_provider.auth_provider_mappings,
user_info: user_info.raw_attributes
)
end end
end end

61
lib/provider_config.rb Normal file
View File

@ -0,0 +1,61 @@
# frozen_string_literal: true
# Deals with the yml file keeping the configuration of the current authentication provider
class ProviderConfig
def initialize
@config = YAML.safe_load_file('config/auth_provider.yml').with_indifferent_access if File.exist?('config/auth_provider.yml')
end
def db
AuthProvider.find(@config[:id])
end
def oidc_config
return nil unless @config[:providable_type] == 'OpenIdConnectProvider'
(@config[:providable_attributes].keys.filter { |n| !n.start_with?('client__') && n != 'profile_url' }.map do |n|
val = @config[:providable_attributes][n]
val.join(' ') if n == 'scope'
[n, val]
end).push(
['client_options', @config[:providable_attributes].keys.filter { |n| n.start_with?('client__') }.to_h do |n|
[n.sub('client__', ''), @config[:providable_attributes][n]]
end]
).to_h
end
def method_missing(method, *args)
return map_value(@config[method]) if @config.key?(method)
return map_value(@config["#{method}_attributes"]) if @config.key?("#{method}_attributes")
super
end
def respond_to_missing?(name)
@config.key?(name) || @config.key("#{name}_attributes")
end
def self.write_active_provider
data = ApplicationController.render(
template: 'auth_provider/provider',
locals: { provider: AuthProvider.active },
handlers: [:jbuilder],
formats: [:json]
)
file_path = Rails.root.join('config/auth_provider.yml')
File.open(file_path, File::WRONLY | File::CREAT) do |file|
file.write(JSON.parse(data).to_yaml)
end
end
private
def map_value(item)
return Struct.new(*item.symbolize_keys.keys).new(*item.values) if item.is_a?(Hash)
return item.map { |v| map_value(v) } if item.is_a?(Array)
item
end
end

View File

@ -41,6 +41,10 @@ namespace :fablab do
User.all.each(&:generate_auth_migration_token) User.all.each(&:generate_auth_migration_token)
end end
# write the configuration to file
require 'provider_config'
ProviderConfig.write_active_provider
# ask the user to restart the application # ask the user to restart the application
next if Rails.env.test? next if Rails.env.test?
@ -71,5 +75,11 @@ namespace :fablab do
task current: :environment do task current: :environment do
puts "Current active authentication provider: #{AuthProvider.active.name}" puts "Current active authentication provider: #{AuthProvider.active.name}"
end end
desc 'write the provider config to a configuration file'
task write_provider: :environment do
require 'provider_config'
ProviderConfig.write_active_provider
end
end end
end end

View File

@ -0,0 +1,36 @@
#!/usr/bin/env bash
yq() {
docker run --rm -i -v "${PWD}:/workdir" --user "$UID" mikefarah/yq:4 "$@"
}
config()
{
echo -ne "Checking user... "
if [[ "$(whoami)" != "root" ]] && ! groups | grep docker
then
echo "Please add your current user to the docker group OR run this script as root."
echo "current user is not allowed to use docker, exiting..."
exit 1
fi
SERVICE="$(yq eval '.services.*.image | select(. == "sleede/fab-manager*") | path | .[-2]' docker-compose.yml)"
echo -e "\n"
}
add_mount()
{
if [[ ! $(yq eval ".services.$SERVICE.volumes.[] | select (. == \"*auth_provider.yml\")" docker-compose.yml) ]]; then
# change docker-compose.yml permissions for fix yq can't modify file issue
chmod 666 docker-compose.yml
yq -i eval ".services.$SERVICE.volumes += [\"\./config/auth_provider.yml:/usr/src/app/auth_provider.yml\"]" docker-compose.yml
chmod 644 docker-compose.yml
fi
}
proceed()
{
config
add_mount
}
proceed "$@"

View File

@ -19,6 +19,7 @@ services:
- ./log:/var/log/supervisor - ./log:/var/log/supervisor
- ./plugins:/usr/src/app/plugins - ./plugins:/usr/src/app/plugins
- ./accounting:/usr/src/app/accounting - ./accounting:/usr/src/app/accounting
- ./config/auth_provider.yml:/usr/src/app/auth_provider.yml
depends_on: depends_on:
- postgres - postgres
- redis - redis