thecore_auth_commons 3.5.13 → 3.5.15

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: 9adb75c5f18c265f09ea114168ffef23573cb3331f0f9a2ed9b7ab87d46f3813
4
- data.tar.gz: f25d391c389a2e521bf7e86314a68837e1ce3b92c028cec9a19492cb232e7160
3
+ metadata.gz: 2a5d019d73046f1ba808347236d3e26474d57f7f72c15e5f616d667271d6164e
4
+ data.tar.gz: bd140a9026819eaca4e6fe0759ade000115b5aa74785ade45d9963985eb753cb
5
5
  SHA512:
6
- metadata.gz: 569320497ba05528a7a972866aa3e2cb742139a331f25b66466786bb55fc8fe33933388fadce770b80d696c9eeb10fd5a63a811abda8d2122b32dc5830f50583
7
- data.tar.gz: 730e678b864fe11d7b6dd40ab3a43a7dd44f591b3a34289d315fa29630eb6f0abab4d858195efd33d41bd7f3aeffac358e0f822f295d0a05e19d593a7df25897
6
+ metadata.gz: 96510a81f013f4ad7503844d9daf60c2f7292b038c43cb245d8fe519c164eadaeab710022593a75a8fd61322f9842c2e2019505dfdc39a524745d8c829bce7fc
7
+ data.tar.gz: d2992fec30b632567a9781fe5d3af2d479f6829d6390ee25403f2da932a611ecaf57c915a00606e8b010bc3e7b62803ca5375e3c2ed4e4a4e1f566d7264bfdfb
data/README.md CHANGED
@@ -1,6 +1,127 @@
1
- This is part of Thecore framework: https://github.com/gabrieletassoni/thecore/tree/release/3
1
+ # thecore_auth_commons
2
2
 
3
- It exposes two Env vars to chage the behaviour of Devise at startup:
3
+ Part of the [Thecore framework](https://github.com/gabrieletassoni/thecore/tree/release/3).
4
4
 
5
- - MIN_PASSWORD_LENGTH: the minimum length of the password (default: 8)
6
- - SESSION_TIMEOUT_IN_MINUTES: the session timeout in minutes (default: 30)
5
+ Provides authentication and role-based authorization for Rails applications in the Thecore ecosystem. Integrates Devise (local + LDAP + OAuth2), CanCanCan with database-driven permissions, and LDAP user synchronization.
6
+
7
+ ## Features
8
+
9
+ - **Devise authentication** — local credentials, LDAP, Microsoft Entra ID (Azure AD), Google OAuth2
10
+ - **Role-based authorization** — roles assigned to users; permissions (predicate + action + target) assigned to roles; resolved at runtime by CanCanCan
11
+ - **LDAP sync** — import users and groups from one or more LDAP/AD servers; group membership maps to Roles
12
+ - **Dynamic SMTP-less** — no restart needed to update auth settings; reads from ThecoreSettings at runtime
13
+
14
+ ## Environment variables
15
+
16
+ | Variable | Default | Purpose |
17
+ |---|---|---|
18
+ | `MIN_PASSWORD_LENGTH` | `8` | Minimum password length enforced by Devise |
19
+ | `SESSION_TIMEOUT_IN_MINUTES` | `31` | Idle session timeout |
20
+ | `ENTRA_CLIENT_ID` | — | Microsoft Entra ID (Azure AD) OAuth2 client ID |
21
+ | `ENTRA_CLIENT_SECRET` | — | Microsoft Entra ID OAuth2 client secret |
22
+ | `ENTRA_TENANT_ID` | — | Microsoft Entra ID tenant ID |
23
+ | `GOOGLE_CLIENT_ID` | — | Google OAuth2 client ID |
24
+ | `GOOGLE_CLIENT_SECRET` | — | Google OAuth2 client secret |
25
+ | `BASE_DOMAIN` | `example.com` | Used to build the default admin email at seed time |
26
+ | `ADMIN_PASSWORD` | `Change#1` | Default admin password at seed time |
27
+
28
+ OmniAuth providers are registered only when their respective env vars are all present.
29
+
30
+ ## Authorization model
31
+
32
+ ```
33
+ User ──< RoleUser >── Role ──< PermissionRole >── Permission
34
+ │
35
+ ┌─────┼─────┐
36
+ Predicate Action Target
37
+ ```
38
+
39
+ ### Tables
40
+
41
+ | Table | Content |
42
+ |---|---|
43
+ | `predicates` | `can`, `cannot` |
44
+ | `actions` | `manage`, `create`, `read`, `update`, `destroy` |
45
+ | `targets` | `all`, plus one row per `ApplicationRecord` subclass (underscore-cased) |
46
+ | `permissions` | Combination of one predicate + one action + one target (unique triplet) |
47
+ | `permission_roles` | Join between `permissions` and `roles` |
48
+ | `roles` | Named roles |
49
+ | `role_users` | Join between `roles` and `users` |
50
+
51
+ ### How permissions are resolved
52
+
53
+ At login, `Ability#initialize` runs three layers in order (CanCan **last-wins**):
54
+
55
+ 1. `Abilities::ThecoreAuthCommons` — hardcoded base: admins get `can :manage, :all`; nobody can `create Action`; no one can destroy their own User record.
56
+ 2. All other `Abilities::*` modules defined in the host app or other engines.
57
+ 3. Database-driven permissions — every `Permission` linked to the current user (via their roles) is translated into a live CanCan call:
58
+
59
+ ```ruby
60
+ self.send(predicate.name, action.name, target.name.classify.constantize)
61
+ # e.g. can(:manage, Task) / cannot(:destroy, User)
62
+ ```
63
+
64
+ Permissions are applied in ascending `id` order, so a later `cannot` can override an earlier `can`.
65
+
66
+ ### Seeding
67
+
68
+ `db/seeds.rb` populates the three vocabulary tables on first run:
69
+
70
+ ```ruby
71
+ predicates: [:can, :cannot]
72
+ actions: [:manage, :create, :read, :update, :destroy]
73
+ targets: ["all"] + ApplicationRecord.subclasses.map { |m| m.to_s.underscore }
74
+ ```
75
+
76
+ It also creates the first admin user (`admin@BASE_DOMAIN` / `ADMIN_PASSWORD`) if no admin exists yet.
77
+
78
+ To add a permission via console:
79
+
80
+ ```ruby
81
+ perm = Permission.create!(
82
+ predicate: Predicate.find_by(name: "can"),
83
+ action: Action.find_by(name: "manage"),
84
+ target: Target.find_by(name: "task")
85
+ )
86
+ role = Role.find_or_create_by(name: "manager")
87
+ PermissionRole.create!(role: role, permission: perm)
88
+ user.roles << role
89
+ ```
90
+
91
+ ## Authentication flow
92
+
93
+ ### Local + LDAP fallback
94
+
95
+ `Users::SessionsController#create` tries Devise first; if that fails it tries `Ldap::Authenticator`. On successful LDAP auth the user is created/updated via `ThecoreAuthCommons.align_user` and signed in.
96
+
97
+ ### LDAP servers (`LdapServer` model)
98
+
99
+ Multiple servers are supported, ordered by `priority` (ascending). Each server configures:
100
+
101
+ - `host`, `port`, `use_ssl`, `base_dn`, `admin_user`, `admin_password`
102
+ - `auth_field` — LDAP attribute used as login (e.g. `mail`, `sAMAccountName`)
103
+ - `name`, `surname`, `phone`, `code` — LDAP attributes mapped to User fields
104
+
105
+ On LDAP login or import, `memberOf` groups are read; matching group names become `Role` records assigned to the user. Groups named `Administrators`, `Domain Admins`, `Schema Admins`, `Enterprise Admins`, `admins`, or `administrators` also grant `user.admin = true`.
106
+
107
+ Deleting a `LdapServer` record destroys all users whose `auth_source` is `"ldap #{id}"`.
108
+
109
+ ### Background LDAP import
110
+
111
+ `BackgroundLdapImportJob` calls `ThecoreAuthCommons.import_ldap_users_task`, which iterates all `LdapServer` records and upserts matching users. Uses the same queue name as the host app (`#{COMPOSE_PROJECT_NAME}_default`).
112
+
113
+ ### OAuth2 (Entra ID / Google)
114
+
115
+ Handled by `Users::OmniauthCallbacksController`. On callback, `ThecoreAuthCommons.check_user` finds or creates the user (with a random secure password) and sets `auth_source` to `'google'` or `'microsoft'`. New OAuth users are created as admin.
116
+
117
+ ## Password policy
118
+
119
+ Passwords must contain at least one uppercase letter, one lowercase letter, one digit, and one special character. Minimum length is controlled by `MIN_PASSWORD_LENGTH`.
120
+
121
+ `ThecoreAuthCommons.generate_secure_password(length = 20)` generates a compliant random password (used for LDAP/OAuth users who never type their password locally).
122
+
123
+ ## User model validations
124
+
125
+ - Cannot remove admin flag from the last remaining admin.
126
+ - Cannot lock the last non-locked account.
127
+ - Email must match a standard address format and be unique (case-insensitive).
@@ -12,7 +12,9 @@ module Api::LdapServer
12
12
  # - methods: include the result of some method defined in the model
13
13
  # - include: include associated models, it's an object {} which also accepts the keys described here
14
14
  cattr_accessor :json_attrs
15
- self.json_attrs = ::ModelDrivenApi.smart_merge (json_attrs || {}), {}
15
+ # No ::ModelDrivenApi.smart_merge here: this is the lowest Thecore layer and model_driven_api
16
+ # (which sits above it) is not a dependency; merging {} was a no-op anyway.
17
+ self.json_attrs = json_attrs || {}
16
18
 
17
19
  # Custom action callable by the API must be defined in /app/models/concerns/endpoints/
18
20
  end
@@ -2,9 +2,13 @@ module RailsAdmin::LdapServer
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- rails_admin do
6
- navigation_label I18n.t('admin.settings.label')
7
- navigation_icon 'fa fa-passport'
5
+ # rails_admin is not a dependency of this gem (thecore_ui_rails_admin, a layer above, brings
6
+ # it): configure only when RailsAdmin is loaded, so apps/dummies without it can load the model.
7
+ if respond_to?(:rails_admin)
8
+ rails_admin do
9
+ navigation_label I18n.t('admin.settings.label')
10
+ navigation_icon 'fa fa-passport'
11
+ end
8
12
  end
9
13
  end
10
14
  end
@@ -1,3 +1,3 @@
1
1
  module ThecoreAuthCommons
2
- VERSION = "3.5.13".freeze
2
+ VERSION = "3.5.15".freeze
3
3
  end
@@ -1,6 +1,11 @@
1
1
  require "devise"
2
2
  require "cancancan"
3
3
  require "kaminari"
4
+ # activerecord-nulldb-adapter reaches into ActiveRecord::ConnectionAdapters internals
5
+ # (e.g. SqlTypeMetadata::Deduplicable) that aren't necessarily loaded yet under a bare,
6
+ # non-Rails-booted `require` chain (e.g. model_driven_api's plain minitest path) — make sure
7
+ # ActiveRecord itself is fully loaded first.
8
+ require "active_record"
4
9
  require "activerecord-nulldb-adapter"
5
10
  require "thecore_settings"
6
11
  require "net/ldap"
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.5.13
4
+ version: 3.5.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
@@ -212,19 +212,47 @@ dependencies:
212
212
  - !ruby/object:Gem::Version
213
213
  version: '2.18'
214
214
  - !ruby/object:Gem::Dependency
215
- name: sqlite3
215
+ name: pg
216
216
  requirement: !ruby/object:Gem::Requirement
217
217
  requirements:
218
218
  - - "~>"
219
219
  - !ruby/object:Gem::Version
220
- version: '1.4'
220
+ version: '1.1'
221
221
  type: :development
222
222
  prerelease: false
223
223
  version_requirements: !ruby/object:Gem::Requirement
224
224
  requirements:
225
225
  - - "~>"
226
226
  - !ruby/object:Gem::Version
227
- version: '1.4'
227
+ version: '1.1'
228
+ - !ruby/object:Gem::Dependency
229
+ name: actionmailer
230
+ requirement: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - ">="
233
+ - !ruby/object:Gem::Version
234
+ version: '0'
235
+ type: :development
236
+ prerelease: false
237
+ version_requirements: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - ">="
240
+ - !ruby/object:Gem::Version
241
+ version: '0'
242
+ - !ruby/object:Gem::Dependency
243
+ name: activestorage
244
+ requirement: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - ">="
247
+ - !ruby/object:Gem::Version
248
+ version: '0'
249
+ type: :development
250
+ prerelease: false
251
+ version_requirements: !ruby/object:Gem::Requirement
252
+ requirements:
253
+ - - ">="
254
+ - !ruby/object:Gem::Version
255
+ version: '0'
228
256
  description: Provides common User and Role models to attach Authentication and Authorization
229
257
  via your preferred gem.
230
258
  email: