thecore_auth_commons 3.5.13 → 3.5.14

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: f66435a76d0089d6e52de71c3fc7239470ec7c91ca241ba242bce9a5d1bdea1d
4
+ data.tar.gz: b9130fc3d425a86abb0b8c83a0880aedc6be121a2cf634e2e5821320d2a27e2e
5
5
  SHA512:
6
- metadata.gz: 569320497ba05528a7a972866aa3e2cb742139a331f25b66466786bb55fc8fe33933388fadce770b80d696c9eeb10fd5a63a811abda8d2122b32dc5830f50583
7
- data.tar.gz: 730e678b864fe11d7b6dd40ab3a43a7dd44f591b3a34289d315fa29630eb6f0abab4d858195efd33d41bd7f3aeffac358e0f822f295d0a05e19d593a7df25897
6
+ metadata.gz: 5ac374cf84bf7220b9c74f27732bf1f4723b60f11970594c20f6ef9544b00e4e2eaef5e4c0170ea6cb515846958dd05b3ed5cc57f3366ba9fec175678550ad50
7
+ data.tar.gz: a55c49bb2b11e7486d3603b8f9719380cdb535f2dd1f36d1f746643afe6f269747cbaa51f6a8fed362db379426990bd6dbbfab352384f86826a466afd270f78f
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).
@@ -1,3 +1,3 @@
1
1
  module ThecoreAuthCommons
2
- VERSION = "3.5.13".freeze
2
+ VERSION = "3.5.14".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.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
@@ -215,16 +215,44 @@ dependencies:
215
215
  name: sqlite3
216
216
  requirement: !ruby/object:Gem::Requirement
217
217
  requirements:
218
- - - "~>"
218
+ - - ">="
219
219
  - !ruby/object:Gem::Version
220
- version: '1.4'
220
+ version: '0'
221
221
  type: :development
222
222
  prerelease: false
223
223
  version_requirements: !ruby/object:Gem::Requirement
224
224
  requirements:
225
- - - "~>"
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: '0'
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
+ - - ">="
226
247
  - !ruby/object:Gem::Version
227
- version: '1.4'
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: