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 +4 -4
- data/README.md +125 -4
- data/lib/thecore_auth_commons/version.rb +1 -1
- data/lib/thecore_auth_commons.rb +5 -0
- metadata +33 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f66435a76d0089d6e52de71c3fc7239470ec7c91ca241ba242bce9a5d1bdea1d
|
|
4
|
+
data.tar.gz: b9130fc3d425a86abb0b8c83a0880aedc6be121a2cf634e2e5821320d2a27e2e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5ac374cf84bf7220b9c74f27732bf1f4723b60f11970594c20f6ef9544b00e4e2eaef5e4c0170ea6cb515846958dd05b3ed5cc57f3366ba9fec175678550ad50
|
|
7
|
+
data.tar.gz: a55c49bb2b11e7486d3603b8f9719380cdb535f2dd1f36d1f746643afe6f269747cbaa51f6a8fed362db379426990bd6dbbfab352384f86826a466afd270f78f
|
data/README.md
CHANGED
|
@@ -1,6 +1,127 @@
|
|
|
1
|
-
|
|
1
|
+
# thecore_auth_commons
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Part of the [Thecore framework](https://github.com/gabrieletassoni/thecore/tree/release/3).
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
|
|
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).
|
data/lib/thecore_auth_commons.rb
CHANGED
|
@@ -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.
|
|
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: '
|
|
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: '
|
|
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:
|