thecore_auth_commons 3.3.3 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bce6c783c7e44a377fb7b7dbf2305dd23b563eae7e2c8897200fc152c2be0884
4
- data.tar.gz: e7c41b6d7a829023e02fff2f1f6fd194f1686576de3742665b502adc42461e08
3
+ metadata.gz: d5ff5d59febf329c917067988bc9a854f77222f310d07a34b98adbf500b92abd
4
+ data.tar.gz: ece767087f4e4bde62d0500c8e7b9840b0482043401d3df2052009b32cdfe639
5
5
  SHA512:
6
- metadata.gz: 6960fc5c2750f3bf78e59360a8d3b1ef10eaefb179148701d3fb9c53b9f1f917390fe9835e8b12d45185d1351373a6d7054bede15e66795e1668794c76b2930d
7
- data.tar.gz: 5ff74462f5bdc2c9289e6c8c4201f92a74a3f4ca2377138bef0c7a5c76768896c05c64e931bdd2705a794c41ed983d922fdaf968771b7dcfe2055ec29a80ec0f
6
+ metadata.gz: fc4901ac970bc086ae02863f9d82bfb72028055193d3bb6190d0b2396e8c86adf261b8ac6eb101b5de971729f734da6b8a1692454654c9731f3c868d07aed9d9
7
+ data.tar.gz: 7cf606e7d94bf1ef8c404b7bf859130c9d33c06a4d20cc4228dad1d1f035db5b1a99746504a5d92280fbdf818cfe13716ca2c99a21535445d712912063653705
@@ -0,0 +1,35 @@
1
+ # app/controllers/users/sessions_controller.rb
2
+ class Users::SessionsController < Devise::SessionsController
3
+ def create
4
+ self.resource = warden.authenticate(auth_options)
5
+
6
+ if resource
7
+ sign_in_and_redirect(resource)
8
+ else
9
+ user = Ldap::Authenticator.new(
10
+ email: params[:user][:email],
11
+ password: params[:user][:password]
12
+ ).authenticate
13
+
14
+ if user
15
+ flash[:notice] = "Autenticato via LDAP"
16
+ sign_in(:user, user)
17
+ redirect_to after_sign_in_path_for(user)
18
+ else
19
+ flash.now[:alert] = "Email o password non validi"
20
+ self.resource = resource_class.new(sign_in_params)
21
+ clean_up_passwords(resource)
22
+ respond_with_navigational(resource) { render :new, status: :unauthorized }
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def sign_in_and_redirect(resource)
30
+ set_flash_message!(:notice, :signed_in)
31
+ sign_in(resource_name, resource)
32
+ yield resource if block_given?
33
+ respond_with resource, location: after_sign_in_path_for(resource)
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ class BackgroundLdapImportJob < ApplicationJob
2
+ queue_as "#{ENV["COMPOSE_PROJECT_NAME"]}_default".to_sym
3
+
4
+ def perform
5
+ ThecoreAuthCommons.import_ldap_users_task
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module Api::LdapServer
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # Use self.json_attrs to drive json rendering for
6
+ # API model responses (index, show and update ones).
7
+ # For reference:
8
+ # https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
9
+ # The object passed accepts only these keys:
10
+ # - only: list [] of model fields to be shown in JSON serialization
11
+ # - except: exclude these fields from the JSON serialization, is a list []
12
+ # - methods: include the result of some method defined in the model
13
+ # - include: include associated models, it's an object {} which also accepts the keys described here
14
+ cattr_accessor :json_attrs
15
+ self.json_attrs = ::ModelDrivenApi.smart_merge (json_attrs || {}), {}
16
+
17
+ # Custom action callable by the API must be defined in /app/models/concerns/endpoints/
18
+ end
19
+ end
@@ -0,0 +1,78 @@
1
+ class Endpoints::LdapServer < NonCrudEndpoints
2
+ # self.desc 'LdapServer', :test, {
3
+ # # Define the action name using openapi swagger format
4
+ # get: {
5
+ # summary: "Test API Custom Action",
6
+ # description: "This is a test API custom action",
7
+ # operationId: "test",
8
+ # tags: ["Test"],
9
+ # parameters: [
10
+ # {
11
+ # name: "explain",
12
+ # in: "query",
13
+ # description: "Explain the action by returning this openapi schema",
14
+ # required: true,
15
+ # schema: {
16
+ # type: "boolean"
17
+ # }
18
+ # }
19
+ # ],
20
+ # responses: {
21
+ # 200 => {
22
+ # description: "The openAPI json schema for this action",
23
+ # content: {
24
+ # "application/json": {
25
+ # schema: {
26
+ # type: "object",
27
+ # additionalProperties: true
28
+ # }
29
+ # }
30
+ # }
31
+ # },
32
+ # 501 => {
33
+ # error: :string,
34
+ # }
35
+ # }
36
+ # },
37
+ # post: {
38
+ # summary: "Test API Custom Action",
39
+ # description: "This is a test API custom action",
40
+ # operationId: "test",
41
+ # tags: ["Test"],
42
+ # requestBody: {
43
+ # required: true,
44
+ # content: {
45
+ # "application/json": {}
46
+ # }
47
+ # },
48
+ # responses: {
49
+ # 200 => {
50
+ # description: "The openAPI json schema for this action",
51
+ # # This will return the object with a message string and a params object
52
+ # content: {
53
+ # "application/json": {
54
+ # schema: {
55
+ # type: "object",
56
+ # properties: {
57
+ # message: {
58
+ # type: "string"
59
+ # },
60
+ # params: {
61
+ # type: "object",
62
+ # additionalProperties: true
63
+ # }
64
+ # }
65
+ # }
66
+ # }
67
+ # }
68
+ # },
69
+ # 501 => {
70
+ # error: :string,
71
+ # }
72
+ # }
73
+ # }
74
+ # }
75
+ # def test(params)
76
+ # return { message: "Hello World From Test API Custom Action called test", params: params }, 200
77
+ # end
78
+ end
@@ -0,0 +1,10 @@
1
+ module RailsAdmin::LdapServer
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ rails_admin do
6
+ navigation_label I18n.t('admin.settings.label')
7
+ navigation_icon 'fa fa-passport'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,46 @@
1
+ class LdapServer < ApplicationRecord
2
+ default_scope { order(priority: :asc) }
3
+
4
+ # Associations
5
+ include Api::LdapServer
6
+ include RailsAdmin::LdapServer
7
+
8
+ # Validations
9
+ validates :host, presence: true
10
+ validates :base_dn, presence: true
11
+
12
+ # Callbacks
13
+ # After the record is actually deleted from the DB, remove all associated users which have the same auth_source with value "ldap #{id}"
14
+ after_destroy :remove_users_with_auth_source
15
+
16
+ def test_connection
17
+ # Test the connection to the LDAP server
18
+ ldap = Net::LDAP.new(
19
+ host: host,
20
+ port: port,
21
+ encryption: use_ssl ? :simple_tls : nil,
22
+ auth: {
23
+ method: :simple,
24
+ username: admin_user,
25
+ password: admin_password
26
+ }
27
+ )
28
+ # Perform a simple bind to check the connection
29
+ if ldap.bind
30
+ # Connection successful
31
+ Rails.logger.info "Connection to LDAP server #{host} successful."
32
+ else
33
+ # Connection failed
34
+ Rails.logger.info "Connection to LDAP server #{host} failed: #{ldap.get_operation_result.message}"
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # This method is called after the record is destroyed
41
+ def remove_users_with_auth_source
42
+ User.where(auth_source: "ldap #{id}").find_each do |user|
43
+ user.destroy
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,59 @@
1
+ # app/services/ldap/authenticator.rb
2
+ module Ldap
3
+ class Authenticator
4
+ def initialize(email:, password:)
5
+ @password = password
6
+ @email = email
7
+ end
8
+
9
+ def authenticate
10
+ return nil if @password.blank?
11
+
12
+ LdapServer.all.each do |server|
13
+ ldap = Net::LDAP.new(
14
+ host: server.host,
15
+ port: server.port,
16
+ encryption: server.use_ssl ? :simple_tls : nil,
17
+ auth: {
18
+ method: :simple,
19
+ username: server.admin_user,
20
+ password: server.admin_password
21
+ }
22
+ )
23
+
24
+ filter = Net::LDAP::Filter.eq(server.auth_field, email) # server.auth_field
25
+ treebase = server.base_dn
26
+
27
+ ldap.search(base: treebase, filter: filter) do |entry|
28
+ user_dn = entry.dn
29
+
30
+ # Prova autenticazione utente
31
+ user_ldap = Net::LDAP.new(
32
+ host: server.host,
33
+ port: server.port,
34
+ encryption: server.use_ssl ? :simple_tls : nil,
35
+ auth: {
36
+ method: :simple,
37
+ username: user_dn,
38
+ password: password
39
+ }
40
+ )
41
+
42
+ if user_ldap.bind
43
+ return find_or_create_user(entry, server.id)
44
+ end
45
+ end
46
+ end
47
+
48
+ nil
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :email, :password
54
+
55
+ def find_or_create_user(entry, server_id)
56
+ ThecoreAuthCommons.align_user email, entry, server_id
57
+ end
58
+ end
59
+ end
@@ -13,6 +13,9 @@ it:
13
13
  cannot_delete_last_role: "deve esistere almeno un ruolo"
14
14
  activerecord:
15
15
  models:
16
+ ldap_server:
17
+ one: Server LDAP
18
+ other: Server LDAP
16
19
  user:
17
20
  one: Utente
18
21
  other: Utenti
@@ -23,6 +26,23 @@ it:
23
26
  one: Permesso
24
27
  other: Permessi
25
28
  attributes:
29
+ ldap_server:
30
+ # t.string :host, null: false
31
+ # t.integer :port, default: 389
32
+ # t.string :base_dn, null: false
33
+ # t.string :admin_user
34
+ # t.string :admin_password
35
+ # t.integer :priority, default: 1
36
+ # t.boolean :use_ssl, default: false
37
+ # t.string :auth_field, default: "userPrincipalName"
38
+ host: Host
39
+ port: Porta
40
+ base_dn: Base DN
41
+ admin_user: Utente Amministratore
42
+ admin_password: Password Amministratore
43
+ priority: Priorità
44
+ use_ssl: Usa SSL?
45
+ auth_field: Campo di Autenticazione
26
46
  user:
27
47
  email: E-Mail
28
48
  code: Codice
@@ -0,0 +1,21 @@
1
+ class CreateLdapServers < ActiveRecord::Migration[7.2]
2
+ def change
3
+ create_table :ldap_servers do |t|
4
+ t.string :host, null: false
5
+ t.integer :port, default: 389
6
+ t.string :base_dn, null: false
7
+ t.string :admin_user
8
+ t.string :admin_password
9
+ t.integer :priority, default: 1
10
+ t.boolean :use_ssl, default: false
11
+ t.string :auth_field, default: "userPrincipalName"
12
+
13
+ t.timestamps
14
+ end
15
+ add_index :ldap_servers, :host
16
+ add_index :ldap_servers, :base_dn
17
+ add_index :ldap_servers, :admin_user
18
+ add_index :ldap_servers, :admin_password
19
+ add_index :ldap_servers, :auth_field
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ class AddAuthSourceToUser < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_column :users, :auth_source, :string, null: false, default: 'local'
4
+ add_index :users, :auth_source
5
+
6
+ # Fill the new column with the default value for existing users
7
+ reversible do |dir|
8
+ dir.up do
9
+ execute <<-SQL.squish
10
+ UPDATE users
11
+ SET auth_source = 'local'
12
+ WHERE auth_source IS NULL
13
+ SQL
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ # lib/tasks/ldap.rake
2
+ namespace :ldap do
3
+ desc "Importa utenti da LDAP e sincronizzali nel database locale"
4
+ task sync_users: :environment do
5
+ ThecoreAuthCommons.import_ldap_users_task
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module ThecoreAuthCommons
2
- VERSION = "3.3.3".freeze
2
+ VERSION = "3.4.0".freeze
3
3
  end
@@ -3,11 +3,80 @@ require 'cancancan'
3
3
  require 'kaminari'
4
4
  require 'activerecord-nulldb-adapter'
5
5
  require "thecore_settings"
6
+ require "net/ldap"
6
7
 
7
8
  require "thecore_auth_commons/engine"
8
9
 
9
10
  require "thecore/seed"
10
11
 
11
12
  module ThecoreAuthCommons
13
+
14
+ def self.import_ldap_users_task
15
+ puts "== Avvio sincronizzazione utenti da LDAP =="
16
+
17
+ imported_count = 0
18
+
19
+ LdapServer.all.each do |server|
20
+ puts "Contatto server LDAP: #{server.host} (priorità: #{server.priority})"
21
+
22
+ ldap = Net::LDAP.new(
23
+ host: server.host,
24
+ port: server.port,
25
+ encryption: server.use_ssl ? :simple_tls : nil,
26
+ auth: {
27
+ method: :simple,
28
+ username: server.admin_user,
29
+ password: server.admin_password
30
+ }
31
+ )
32
+
33
+ unless ldap.bind
34
+ puts "❌ Connessione fallita a #{server.host}"
35
+ next
36
+ end
37
+
38
+ filter = Net::LDAP::Filter.present(server.auth_field)
39
+ treebase = server.base_dn
40
+
41
+ ldap.search(base: treebase, filter: filter) do |entry|
42
+ email = entry[server.auth_field]&.first
43
+ next unless email
44
+
45
+ puts "Importando utente: #{email}"
46
+
47
+ # Password must contain at least one uppercase letter, one lowercase letter, one number and one special character
48
+ ThecoreAuthCommons.align_user email, entry, server.id
49
+ imported_count += 1
50
+ end
51
+ end
52
+
53
+ puts "== Completato. Utenti importati: #{imported_count} =="
54
+ end
12
55
  # Your code goes here...
56
+ def self.align_user email, entry, server_id
57
+ user = User.find_or_initialize_by(email: email)
58
+ user.auth_source = "ldap #{server_id}"
59
+
60
+ # Password don't need to be changed, just created, otherwise it will invalidate the current user session if it's logged in
61
+ user.password = user.password_confirmation = Devise.friendly_token[0, 20] if user.new_record?
62
+
63
+ # Eventuale mapping LDAP -> campi User
64
+ user.name = entry[:givenname]&.first if user.respond_to?(:name)
65
+
66
+ # Recupera dala entry i gruppi di cui fa parte l'utente e crea i relativi record in Role assegnandoli all'utente corrente
67
+ is_admin = false
68
+ entry[:memberOf].each do |group|
69
+ group_name = group.split(",").first.split("=").last
70
+ # Se il gruppo è un admin, assegna il ruolo admin
71
+ is_admin = true if [ "Administrators" "Domain Admins", "Schema Admins", "Enterprise Admins", "admins", "administrators" ].include?(group_name)
72
+
73
+ role = Role.find_or_create_by(name: group_name)
74
+ user.roles << role unless user.roles.include?(role)
75
+ end
76
+
77
+ user.admin = is_admin if user.respond_to?(:admin)
78
+ # Se l'utente è nuovo o ha cambiato qualcosa, salvalo
79
+ puts "Cannot save user #{email} with errors: #{user.errors.full_messages.join(", ")}" unless user.save # if user.new_record? || user.changed? || user.roles_changed?
80
+ user
81
+ end
13
82
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thecore_auth_commons
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.3
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: net-ldap
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: devise
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -179,7 +193,13 @@ extra_rdoc_files: []
179
193
  files:
180
194
  - README.md
181
195
  - Rakefile
196
+ - app/controllers/users/sessions_controller.rb
197
+ - app/jobs/background_ldap_import_job.rb
182
198
  - app/models/action.rb
199
+ - app/models/concerns/api/ldap_server.rb
200
+ - app/models/concerns/endpoints/ldap_server.rb
201
+ - app/models/concerns/rails_admin/ldap_server.rb
202
+ - app/models/ldap_server.rb
183
203
  - app/models/permission.rb
184
204
  - app/models/permission_role.rb
185
205
  - app/models/predicate.rb
@@ -187,6 +207,7 @@ files:
187
207
  - app/models/role_user.rb
188
208
  - app/models/target.rb
189
209
  - app/models/user.rb
210
+ - app/services/ldap/authenticator.rb
190
211
  - config/initializers/abilities.rb
191
212
  - config/initializers/add_to_db_migrations.rb
192
213
  - config/initializers/after_initialize.rb
@@ -203,7 +224,10 @@ files:
203
224
  - db/migrate/20160209153811_create_roles.rb
204
225
  - db/migrate/20160209153813_create_role_users.rb
205
226
  - db/migrate/20160209153816_create_permissions_chain.rb
227
+ - db/migrate/20250516074016_create_ldap_servers.rb
228
+ - db/migrate/20250516075204_add_auth_source_to_user.rb
206
229
  - db/seeds.rb
230
+ - lib/tasks/ldap.rake
207
231
  - lib/tasks/thecore_auth_commons_tasks.rake
208
232
  - lib/thecore/seed.rb
209
233
  - lib/thecore_auth_commons.rb