model_driven_api 3.3.2 → 3.4.1

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: 5e72c48257faa2dde0c1c5766b28b4162577fa95c6ae6d9ce1e70f707d067516
4
- data.tar.gz: 9bf11f22033d32dae78e29ea992649e5d7f9e94039b346ce87a4592101fd85f2
3
+ metadata.gz: 947535ba94bce4b6b7b3eaed83f5ffd9b624781de13151a8e876bbf490df057a
4
+ data.tar.gz: a0626e40d22e148fb4b1e15956a28ed90c0a1aca332a2bbb67a01b13726ad2d6
5
5
  SHA512:
6
- metadata.gz: c42e7720439a5d9b1ac17976f7cd02b8135eda4bf49a953fd217d3f3b997f697773f8d7fce49c962f6318d57b9d2e2b5ce23f180a8796e5f24967602e309505a
7
- data.tar.gz: 50891ee25885ebb710ef79a1b100d35a9b63ba300a460b750d0aa62e41e4ecc3772d5780ed460309e2e332ea6782b5913dd8531a9e0a612b5552d80cef240eec
6
+ metadata.gz: 7ead1586c1e81df0632b582f72e6e94ca39235f56ba3c2e1700db809514246120e23d2865a62de6bd48252d4d0d6f7e6fb1a57d76541a1c59b43153534ff2ab1
7
+ data.tar.gz: '0656608baf5a2ba86b4b3eff6a585274c6fbf53bfb6108be2479e681e8a726158cc066489c433482cc1d2eca7822b0d8d76cab7c99ad31f16d12754f564ae492'
data/README.md CHANGED
@@ -1 +1,66 @@
1
1
  This is part of Thecore framework: https://github.com/gabrieletassoni/thecore/tree/release/3
2
+
3
+ ## Authorization
4
+
5
+ It's enabled to LDAP (Active Directory and othe LDAP Based local service) and to oauth2 (Google Workspace and Microsoft Entra ID) services.
6
+
7
+ ### 📘 Register OAuth Apps
8
+
9
+ To use OAuth2 for authentication, you need to register your application with the OAuth provider (Google or Microsoft). Follow these steps, which are specific to each provider and aimed at a demo frontend application running on `http://localhost:5173`.
10
+
11
+ #### ✅ Google OAuth Client Setup
12
+
13
+ Go to: https://console.cloud.google.com/apis/credentials
14
+ Create OAuth 2.0 Client ID
15
+ Choose Web Application
16
+ Add to “Authorized JavaScript Origins”:
17
+ http://localhost:5173
18
+ Add to “Authorized redirect URIs” (used by Google, not backend):
19
+ http://localhost:5173 (for frontend access only)
20
+ Save your Client ID
21
+
22
+
23
+ #### ✅ Microsoft Entra ID OAuth Setup
24
+
25
+ Go to https://portal.azure.com
26
+ Microsoft Entra ID → App registrations → New registration
27
+ Set redirect URI to http://localhost:5173 (type: SPA)
28
+ Note the:
29
+ Application (client) ID
30
+ Directory (tenant) ID
31
+ Under Authentication tab:
32
+ Add platform: “Single-page application”
33
+ Add http://localhost:5173 to Redirect URIs
34
+
35
+ ### Save your Client ID and Tenant ID
36
+ You will need these IDs to configure the backend.
37
+
38
+ ### 📘 Configure OAuth in Thecore
39
+ To configure OAuth in Thecore backend, you need to set the following environment variables in your `.env` file or `environmet:` configuration in docker-compose.yml:
40
+
41
+ ```plaintext
42
+ # OAuth Configuration
43
+ # Microsoft OAuth
44
+ ENTRA_CLIENT_ID: "your-client-id"
45
+ ENTRA_CLIENT_SECRET: "your-client-secret"
46
+ ENTRA_TENANT_ID: "your-tenant-id"
47
+
48
+ # Google OAuth
49
+ GOOGLE_CLIENT_ID: "your-client-id.apps.googleusercontent.com"
50
+ GOOGLE_CLIENT_SECRET: "your-client-secret"
51
+ ```
52
+
53
+ In the Frontend applciation, you will need to set the following environment variables in your `.env` file:
54
+
55
+ ```plaintext
56
+ # OAuth Configuration
57
+ # Google OAuth
58
+ VITE_GOOGLE_CLIENT_ID=your-client-id
59
+
60
+ # Microsoft OAuth
61
+ VITE_AZURE_CLIENT_ID=your-client-id
62
+ VITE_AZURE_TENANT_ID=your-tenant-id
63
+
64
+ # OAuth Callback URLs
65
+ VITE_API_URL=http://yourdomain/api/v2/auth/google_oauth2/callback
66
+ ```
@@ -37,6 +37,9 @@ class AuthenticateUser
37
37
  user = nil if user.blank? || user.authenticate(password).blank?
38
38
  end
39
39
 
40
+ #Try to authenticate the user against LDAP if user is not present at this point
41
+ user = Ldap::Authenticator.new(email: email, password: password).authenticate if user.blank?
42
+
40
43
  raise AccessDenied unless user.present?
41
44
 
42
45
  return user
@@ -0,0 +1,71 @@
1
+ module Api::V2::Auth
2
+ class OauthController < ActionController::API
3
+ def callback
4
+ email = params['email']
5
+
6
+ user = User.find_or_create_by(email: email) do |u|
7
+ u.name = params['given_name']
8
+ u.surname = params['family_name']
9
+ u.password = u.password_confirmation = ThecoreAuthCommons.generate_secure_password
10
+ u.auth_source = params['provider'] # 'google' or 'microsoft'
11
+ u.admin = true
12
+ end
13
+ unless user
14
+ render json: { error: "User not registered" }, status: :unauthorized
15
+ return
16
+ end
17
+
18
+ token = JsonWebToken.encode(user_id: user.id)
19
+
20
+ if ENV["ALLOW_MULTISESSIONS"] == "false"
21
+ UsedToken.where(user_id: user.id).update_all(is_valid: false)
22
+ UsedToken.create!(token: token, user_id: user.id)
23
+ end
24
+
25
+ # redirect_url = "#{ENV['FRONTEND_URL']}?token=#{token}"
26
+ # redirect_to redirect_url
27
+ response.set_header("Token", JsonWebToken.encode(user_id: user.id))
28
+ render json: user, status: :ok
29
+ end
30
+
31
+ def failure
32
+ render json: { error: "OAuth authentication failed" }, status: :unauthorized
33
+ end
34
+
35
+ def exchange_token
36
+ provider_token = params[:provider_token]
37
+ provider = params[:provider] # 'google' or 'microsoft'
38
+
39
+ user_info = case provider
40
+ when 'google'
41
+ uri = URI("https://www.googleapis.com/oauth2/v3/userinfo")
42
+ res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
43
+ req = Net::HTTP::Get.new(uri)
44
+ req["Authorization"] = "Bearer #{provider_token}"
45
+ http.request(req)
46
+ end
47
+ JSON.parse(res.body)
48
+ when 'microsoft'
49
+ uri = URI("https://graph.microsoft.com/v1.0/me")
50
+ res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
51
+ req = Net::HTTP::Get.new(uri)
52
+ req["Authorization"] = "Bearer #{provider_token}"
53
+ http.request(req)
54
+ end
55
+ JSON.parse(res.body)
56
+ else
57
+ return render json: { error: "Unknown provider" }, status: :unprocessable_entity
58
+ end
59
+
60
+ email = user_info["mail"] || user_info["email"] || user_info["userPrincipalName"]
61
+ user = User.find_by(email: email)
62
+
63
+ if user.nil?
64
+ return render json: { error: "User not registered" }, status: :unauthorized
65
+ end
66
+
67
+ response.set_header("Token", JsonWebToken.encode(user_id: user.id))
68
+ render json: user, status: :ok
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,20 @@
1
+ Rails.application.config.middleware.use OmniAuth::Builder do
2
+ provider(
3
+ :entra_id,
4
+ {
5
+ client_id: ENV['ENTRA_CLIENT_ID'],
6
+ client_secret: ENV['ENTRA_CLIENT_SECRET'],
7
+ tenant_id: ENV['ENTRA_TENANT_ID'], # Needed for Microsoft
8
+ scope: 'User.Read',
9
+ response_type: 'code'
10
+ }
11
+ )
12
+ provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], {
13
+ scope: 'email,profile',
14
+ prompt: 'select_account',
15
+ access_type: 'online'
16
+ }
17
+ end
18
+
19
+ OmniAuth.config.allowed_request_methods = [:get, :post]
20
+ OmniAuth.config.silence_get_warning = true
data/config/routes.rb CHANGED
@@ -1,11 +1,26 @@
1
1
  # require 'ransack'
2
2
 
3
3
  Rails.application.routes.draw do
4
- # REST API (Stateless)
5
-
4
+ oauth_test = (ENV['ENTRA_CLIENT_ID'].present? && ENV['ENTRA_CLIENT_SECRET'].present? && ENV['ENTRA_TENANT_ID'].present?) || (ENV['GOOGLE_CLIENT_ID'].present? && ENV['GOOGLE_CLIENT_SECRET'].present?)
6
5
  scope ENV.fetch("RAILS_RELATIVE_URL_ROOT", "/") do
6
+ if oauth_test
7
+ # OmniAuth callbacks need these top-level paths:
8
+ match '/auth/:provider/callback', to: redirect('/api/v2/auth/%{provider}/callback'), via: [:get, :post]
9
+ match '/auth/failure', to: redirect('/api/v2/auth/failure'), via: [:get, :post]
10
+ end
7
11
  namespace :api, constraints: { format: :json } do
8
12
  namespace :v2 do
13
+ # Authentication via Oauth2 only if the environment variable is set
14
+ if oauth_test
15
+ namespace :auth do
16
+ # Omniauth routes for OAuth2 authentication
17
+ match ':provider/callback', to: 'oauth#callback', via: [:get, :post]
18
+ get :failure, to: 'oauth#failure'
19
+ get ':provider', to: redirect('/auth/%{provider}') # triggers OmniAuth middleware
20
+ post :jwt, to: 'oauth#exchange_token' # Optional endpoint to allow frontends to send the OAuth token
21
+ end
22
+ end
23
+
9
24
  resources :users
10
25
 
11
26
  namespace :info do
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.3.2".freeze
2
+ VERSION = "3.4.1".freeze
3
3
  end
@@ -13,6 +13,10 @@ require 'concerns/api_exception_management'
13
13
 
14
14
  require 'deep_merge/rails_compat'
15
15
 
16
+ require 'omniauth'
17
+ require 'omniauth-google-oauth2'
18
+ require 'omniauth-entra-id'
19
+
16
20
  require "model_driven_api/engine"
17
21
 
18
22
  require "safe_sql_executor"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model_driven_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.2
4
+ version: 3.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
@@ -93,6 +93,48 @@ dependencies:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: '1.2'
96
+ - !ruby/object:Gem::Dependency
97
+ name: omniauth
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.1'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.1'
110
+ - !ruby/object:Gem::Dependency
111
+ name: omniauth-google-oauth2
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '1.2'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1.2'
124
+ - !ruby/object:Gem::Dependency
125
+ name: omniauth-entra-id
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.0'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '3.0'
96
138
  - !ruby/object:Gem::Dependency
97
139
  name: sqlite3
98
140
  requirement: !ruby/object:Gem::Requirement
@@ -121,6 +163,7 @@ files:
121
163
  - app/commands/authenticate_user.rb
122
164
  - app/commands/authorize_api_request.rb
123
165
  - app/controllers/api/v2/application_controller.rb
166
+ - app/controllers/api/v2/auth/oauth_controller.rb
124
167
  - app/controllers/api/v2/authentication_controller.rb
125
168
  - app/controllers/api/v2/info_controller.rb
126
169
  - app/controllers/api/v2/raw_controller.rb
@@ -131,6 +174,7 @@ files:
131
174
  - config/initializers/after_initialize_for_model_driven_api.rb
132
175
  - config/initializers/cors_api_thecore.rb
133
176
  - config/initializers/knock.rb
177
+ - config/initializers/omniauth.rb
134
178
  - config/initializers/time_with_zone.rb
135
179
  - config/initializers/wrap_parameters.rb
136
180
  - config/routes.rb