m365_active_storage 1.0.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 +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +19 -0
- data/README.md +82 -0
- data/Rakefile +32 -0
- data/config/routes.rb +5 -0
- data/config/storage.yml +10 -0
- data/lib/active_storage/authentication.rb +189 -0
- data/lib/active_storage/configuration.rb +155 -0
- data/lib/active_storage/http.rb +215 -0
- data/lib/active_storage/service/sharepoint_service.rb +415 -0
- data/lib/generators/m365_active_storage/check/check_generator.rb +53 -0
- data/lib/generators/m365_active_storage/migrate/migrate_generator.rb +40 -0
- data/lib/m365_active_storage/controllers/blobs_controller.rb +132 -0
- data/lib/m365_active_storage/files.rb +59 -0
- data/lib/m365_active_storage/m365.rb +161 -0
- data/lib/m365_active_storage/pending_delete.rb +77 -0
- data/lib/m365_active_storage/railtie.rb +65 -0
- data/lib/m365_active_storage/version.rb +6 -0
- data/lib/m365_active_storage.rb +62 -0
- data/node_modules/.yarn-integrity +10 -0
- data/sig/m365_active_storage.rbs +4 -0
- data/yarn.lock +4 -0
- metadata +83 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 248f6e95435a9737f6aa06db40f6d758a59a4d6355ad4399a12d5dbc0d24efde
|
|
4
|
+
data.tar.gz: c93ea5f7c16c419e2cfadbc9ce6a679b1a0abb533bb9863b9a75f8b7aed7d17d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 91d7ddd980e2959d4c834847affd9b515f1544c307fa00d562bf6c15b3754fa3c1d657ed648c03eb285eb7cd3a98daf75dc23c5c888bc88d1914fc19cd249528
|
|
7
|
+
data.tar.gz: d8cedd464a2fb9946f778b79b3113e1176aa4d444d56b271bfda1378a386a3765c21850279325a78b3af1a3c5547b5f85a3fe42857652e9b002e3d814cace891
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.4.8
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 Óscar León
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
|
|
19
|
+
Full license text available at: https://www.gnu.org/licenses/gpl-3.0.txt
|
data/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# m365-active-storage
|
|
2
|
+
|
|
3
|
+
Rails ActiveStorage in M365 Sharepoint
|
|
4
|
+
|
|
5
|
+
## Install the gem
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem install m365_active_storage
|
|
9
|
+
```
|
|
10
|
+
or add to the Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem "m365_active_storage"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Configure ActiveStorage
|
|
17
|
+
|
|
18
|
+
### Configure Active Storage and auth
|
|
19
|
+
#### Rails credentials
|
|
20
|
+
```yaml
|
|
21
|
+
sharepoint:
|
|
22
|
+
ms_graph_url:
|
|
23
|
+
ms_graph_version:
|
|
24
|
+
auth_host:
|
|
25
|
+
oauth_tenant:
|
|
26
|
+
oauth_app_id:
|
|
27
|
+
oauth_secret:
|
|
28
|
+
sharepoint_site_id:
|
|
29
|
+
sharepoint_drive_id:
|
|
30
|
+
```
|
|
31
|
+
#### -- or --
|
|
32
|
+
|
|
33
|
+
#### ENV
|
|
34
|
+
```shell
|
|
35
|
+
MS_GRAPH_URL=
|
|
36
|
+
MS_GRAPH_VERSION=
|
|
37
|
+
AUTH_HOST=
|
|
38
|
+
OAUTH_TENANT=
|
|
39
|
+
OAUTH_APP_ID=
|
|
40
|
+
OAUTH_SECRET=
|
|
41
|
+
SHAREPOINT_SITE_ID=
|
|
42
|
+
SHAREPOINT_DRIVE_ID=
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Set active storage to sharepoint service
|
|
46
|
+
In the app config/environments/`<environment>`.rb
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
config.active_storage.service = :sharepoint
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Run the check generator
|
|
53
|
+
```
|
|
54
|
+
$ rails g m365_active_storage:check
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Move files from local to sharepoint with the migrate generator
|
|
58
|
+
```shell
|
|
59
|
+
g m365_active_storage:migrate
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```shell
|
|
63
|
+
5 blobs to migrate
|
|
64
|
+
filename_1 done
|
|
65
|
+
filename_2 done
|
|
66
|
+
filename_3 done
|
|
67
|
+
filename_4 failed: ActiveStorage::FileNotFoundError
|
|
68
|
+
filename_5 done
|
|
69
|
+
...
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> [!NOTE]
|
|
73
|
+
>
|
|
74
|
+
> The local files are still in the storage folder.
|
|
75
|
+
>
|
|
76
|
+
> You must delete them manually after validating the data has been correctly moved.
|
|
77
|
+
>
|
|
78
|
+
|
|
79
|
+
> [!NOTE]
|
|
80
|
+
>
|
|
81
|
+
> Don't forget to restart your server, if running
|
|
82
|
+
>
|
data/Rakefile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "minitest/test_task"
|
|
5
|
+
require "rdoc/task"
|
|
6
|
+
|
|
7
|
+
Minitest::TestTask.create do |t|
|
|
8
|
+
t.framework = %(require "test/test_helper.rb")
|
|
9
|
+
t.test_globs = ["test/**/*_test.rb"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "rubocop/rake_task"
|
|
13
|
+
|
|
14
|
+
RuboCop::RakeTask.new
|
|
15
|
+
|
|
16
|
+
# RDoc task for generating API documentation
|
|
17
|
+
RDoc::Task.new do |rdoc|
|
|
18
|
+
rdoc.title = "M365 Active Storage - SharePoint Storage for Rails"
|
|
19
|
+
rdoc.main = "README.md"
|
|
20
|
+
rdoc.rdoc_files.include("README.md", "CHANGELOG.md", "LICENSE.txt", "lib/**/*.rb")
|
|
21
|
+
rdoc.rdoc_files.exclude("test/**/*", "spec/**/*")
|
|
22
|
+
rdoc.options = [
|
|
23
|
+
"--markup=markdown",
|
|
24
|
+
"--line-numbers",
|
|
25
|
+
"--all",
|
|
26
|
+
"--hyperlink-all"
|
|
27
|
+
]
|
|
28
|
+
rdoc.rdoc_dir = "doc"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
task default: %i[test rubocop]
|
|
32
|
+
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Rails.application.routes.draw do
|
|
2
|
+
# Override default Active Storage blob route to use our authenticated controller
|
|
3
|
+
# This must come BEFORE the default activeStorage routes
|
|
4
|
+
get "/rails/active_storage/blobs/:signed_id/:filename", to: "m365_active_storage/blobs#show"
|
|
5
|
+
end
|
data/config/storage.yml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
sharepoint:
|
|
2
|
+
service: Sharepoint
|
|
3
|
+
ms_graph_url: <%= Rails.application.credentials.dig(:sharepoint, :ms_graph_url) || ENV["MS_GRAPH_URL"] %>
|
|
4
|
+
ms_graph_version: <%= Rails.application.credentials.dig(:sharepoint, :ms_graph_version) || ENV["MS_GRAPH_VERSION"] %>
|
|
5
|
+
auth_host: <%= Rails.application.credentials.dig(:sharepoint, :auth_host) || ENV["AUTH_HOST"] %>
|
|
6
|
+
tenant_id: <%= Rails.application.credentials.dig(:sharepoint, :oauth_tenant) || ENV["OAUTH_TENANT"] %>
|
|
7
|
+
app_id: <%= Rails.application.credentials.dig(:sharepoint, :oauth_app_id) || ENV["OAUTH_APP_ID"] %>
|
|
8
|
+
secret: <%= Rails.application.credentials.dig(:sharepoint, :oauth_secret) || ENV["OAUTH_SECRET"] %>
|
|
9
|
+
site_id: <%= Rails.application.credentials.dig(:sharepoint, :sharepoint_site_id) || ENV["SHAREPOINT_SITE_ID"] %>
|
|
10
|
+
drive_id: <%= Rails.application.credentials.dig(:sharepoint, :sharepoint_drive_id) || ENV["SHAREPOINT_DRIVE_ID"] %>
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M365ActiveStorage
|
|
4
|
+
# == OAuth2 Authentication Handler
|
|
5
|
+
#
|
|
6
|
+
# Manages OAuth2 authentication with Microsoft Azure AD to obtain and maintain
|
|
7
|
+
# access tokens for Microsoft Graph API calls.
|
|
8
|
+
#
|
|
9
|
+
# === Responsibilities
|
|
10
|
+
#
|
|
11
|
+
# * Obtain OAuth2 access tokens using client credentials flow
|
|
12
|
+
# * Cache tokens and automatically refresh when expired
|
|
13
|
+
# * Handle authentication errors and retries
|
|
14
|
+
# * Manage token lifecycle and expiration
|
|
15
|
+
#
|
|
16
|
+
# === Architecture
|
|
17
|
+
#
|
|
18
|
+
# The Authentication class implements the OAuth2 Client Credentials flow:
|
|
19
|
+
# 1. Exchanges client ID and secret for an access token
|
|
20
|
+
# 2. Caches the token with its expiration time
|
|
21
|
+
# 3. Automatically refreshes tokens before expiration
|
|
22
|
+
# 4. Provides token to HTTP requests for API calls
|
|
23
|
+
#
|
|
24
|
+
# === Example Usage
|
|
25
|
+
#
|
|
26
|
+
# config = M365ActiveStorage::Configuration.new(**config_params)
|
|
27
|
+
# auth = M365ActiveStorage::Authentication.new(config)
|
|
28
|
+
#
|
|
29
|
+
# # Ensure we have a valid token before making API calls
|
|
30
|
+
# auth.ensure_valid_token
|
|
31
|
+
#
|
|
32
|
+
# # Token is now available for HTTP requests
|
|
33
|
+
# token = auth.token
|
|
34
|
+
#
|
|
35
|
+
# @attr_reader [Configuration] config The SharePoint configuration
|
|
36
|
+
# @attr_reader [String] token The current OAuth2 access token
|
|
37
|
+
# @attr_reader [Time] token_expires_at The expiration time of the current token
|
|
38
|
+
#
|
|
39
|
+
# @see M365ActiveStorage::Configuration
|
|
40
|
+
# @see M365ActiveStorage::Http
|
|
41
|
+
class Authentication
|
|
42
|
+
attr_reader :config, :token, :token_expires_at
|
|
43
|
+
|
|
44
|
+
# Initialize the Authentication handler
|
|
45
|
+
#
|
|
46
|
+
# @param [Configuration] config The SharePoint configuration object containing
|
|
47
|
+
# authentication parameters (auth_host, tenant_id, app_id, secret)
|
|
48
|
+
def initialize(config)
|
|
49
|
+
@config = config
|
|
50
|
+
@token = nil
|
|
51
|
+
@token_expires_at = nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Ensure a valid, non-expired token is available
|
|
55
|
+
#
|
|
56
|
+
# Checks if the current token is nil or expired. If so, obtains a new token
|
|
57
|
+
# from the Azure AD authentication endpoint. This method is called automatically
|
|
58
|
+
# before making API requests.
|
|
59
|
+
#
|
|
60
|
+
# If a valid token already exists and hasn't expired, this method returns immediately.
|
|
61
|
+
#
|
|
62
|
+
# @return [void]
|
|
63
|
+
# @raise [StandardError] if token retrieval fails
|
|
64
|
+
# @example
|
|
65
|
+
# auth.ensure_valid_token # Obtains token if needed
|
|
66
|
+
# puts auth.token # Token is now available
|
|
67
|
+
#
|
|
68
|
+
# @see #token_expired?
|
|
69
|
+
def ensure_valid_token
|
|
70
|
+
return unless token.blank? || token_expired?
|
|
71
|
+
|
|
72
|
+
obtain_app_token
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Force immediate token expiration
|
|
76
|
+
#
|
|
77
|
+
# Manually expires the current token by setting the expiration time to the past.
|
|
78
|
+
# This is useful for testing or forcing a token refresh.
|
|
79
|
+
#
|
|
80
|
+
# @return [Time] The new expiration time (1 minute in the past)
|
|
81
|
+
# @example
|
|
82
|
+
# auth.expire_token!
|
|
83
|
+
# auth.ensure_valid_token # Will fetch a new token
|
|
84
|
+
#
|
|
85
|
+
# @see #ensure_valid_token
|
|
86
|
+
def expire_token!
|
|
87
|
+
@token_expires_at = Time.current - 1.minute
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
# Build the authentication URL for Azure AD
|
|
93
|
+
#
|
|
94
|
+
# Constructs the OAuth2 token endpoint URL from the configured auth host and tenant ID.
|
|
95
|
+
#
|
|
96
|
+
# @return [String] The complete authentication URL
|
|
97
|
+
# @example
|
|
98
|
+
# # Returns: "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token"
|
|
99
|
+
def auth_url
|
|
100
|
+
"#{config.auth_host}/#{config.tenant_id}/oauth2/v2.0/token"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Parse the authentication URL into a URI object
|
|
104
|
+
#
|
|
105
|
+
# @return [URI] The parsed URI for the authentication endpoint
|
|
106
|
+
def auth_uri
|
|
107
|
+
URI(auth_url)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Build the OAuth2 request body
|
|
111
|
+
#
|
|
112
|
+
# Constructs the form-encoded request body for the client credentials flow,
|
|
113
|
+
# including:
|
|
114
|
+
# * Grant type: "client_credentials"
|
|
115
|
+
# * Client ID from configuration
|
|
116
|
+
# * Client secret from configuration
|
|
117
|
+
# * Scope: Microsoft Graph API default scope
|
|
118
|
+
#
|
|
119
|
+
# @return [String] URL-encoded form data
|
|
120
|
+
# @example
|
|
121
|
+
# # Returns: "grant_type=client_credentials&client_id=...&client_secret=...&scope=..."
|
|
122
|
+
def request_body
|
|
123
|
+
URI.encode_www_form(
|
|
124
|
+
grant_type: "client_credentials",
|
|
125
|
+
client_id: config.app_id,
|
|
126
|
+
client_secret: config.secret,
|
|
127
|
+
scope: "#{config.ms_graph_url}/.default"
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Obtain a new OAuth2 application token
|
|
132
|
+
#
|
|
133
|
+
# Makes an HTTP request to the Azure AD token endpoint using client credentials.
|
|
134
|
+
# Parses the response and updates the token and expiration time.
|
|
135
|
+
#
|
|
136
|
+
# @return [void]
|
|
137
|
+
# @raise [StandardError] if the token request fails (non-200 response)
|
|
138
|
+
# @example
|
|
139
|
+
# # Automatically called by ensure_valid_token
|
|
140
|
+
# auth.send(:obtain_app_token)
|
|
141
|
+
#
|
|
142
|
+
# @see #fetch_token_response
|
|
143
|
+
# @see #parse_token_response
|
|
144
|
+
def obtain_app_token
|
|
145
|
+
response = fetch_token_response
|
|
146
|
+
raise "Failed to obtain SharePoint token" unless response.code.to_i == 200
|
|
147
|
+
|
|
148
|
+
parse_token_response(response)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Fetch the token response from Azure AD
|
|
152
|
+
#
|
|
153
|
+
# Makes an HTTPS POST request to the Azure AD OAuth2 token endpoint
|
|
154
|
+
# with client credentials.
|
|
155
|
+
#
|
|
156
|
+
# @return [Net::HTTPResponse] The response from the token endpoint
|
|
157
|
+
# @raise [StandardError] if the HTTP request fails
|
|
158
|
+
def fetch_token_response
|
|
159
|
+
http = Net::HTTP.new(auth_uri.host, auth_uri.port)
|
|
160
|
+
http.use_ssl = true
|
|
161
|
+
http.post(auth_uri.request_uri, request_body, { "Content-Type" => "application/x-www-form-urlencoded" })
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Parse and store the token response from Azure AD
|
|
165
|
+
#
|
|
166
|
+
# Extracts the access token and expiration time from the JSON response.
|
|
167
|
+
# Automatically subtracts 5 minutes from the expiration time as a safety margin
|
|
168
|
+
# to prevent using nearly-expired tokens.
|
|
169
|
+
#
|
|
170
|
+
# @param [Net::HTTPResponse] response The HTTP response from the token endpoint
|
|
171
|
+
# @return [void]
|
|
172
|
+
# @example
|
|
173
|
+
# response = fetch_token_response
|
|
174
|
+
# parse_token_response(response) # Sets @token and @token_expires_at
|
|
175
|
+
def parse_token_response(response)
|
|
176
|
+
token_data = JSON.parse(response.body)
|
|
177
|
+
expires_in = token_data["expires_in"].to_i
|
|
178
|
+
@token = token_data["access_token"]
|
|
179
|
+
@token_expires_at = Time.current + expires_in.seconds - 5.minutes
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Check if the current token is expired
|
|
183
|
+
#
|
|
184
|
+
# @return [Boolean] true if the token is nil or the expiration time has passed
|
|
185
|
+
def token_expired?
|
|
186
|
+
token_expires_at.nil? || Time.current >= token_expires_at
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M365ActiveStorage
|
|
4
|
+
# == SharePoint Configuration Manager
|
|
5
|
+
#
|
|
6
|
+
# Manages and validates all configuration parameters needed to connect to Microsoft 365 SharePoint
|
|
7
|
+
# via the Microsoft Graph API.
|
|
8
|
+
#
|
|
9
|
+
# === Responsibilities
|
|
10
|
+
#
|
|
11
|
+
# * Load and parse configuration parameters
|
|
12
|
+
# * Validate that all required parameters are present
|
|
13
|
+
# * Provide convenient accessors for configuration values
|
|
14
|
+
# * Raise informative errors for missing or invalid configurations
|
|
15
|
+
#
|
|
16
|
+
# === Required Configuration Parameters
|
|
17
|
+
#
|
|
18
|
+
# * +ms_graph_url+ - The Microsoft Graph API base URL (typically https://graph.microsoft.com)
|
|
19
|
+
# * +ms_graph_version+ - The Graph API version (e.g., "v1.0", "beta")
|
|
20
|
+
# * +auth_host+ - The OAuth2 authentication endpoint (typically https://login.microsoftonline.com)
|
|
21
|
+
# * +tenant_id+ - Your Azure AD tenant ID (directory ID from Azure Portal)
|
|
22
|
+
# * +app_id+ - Your Azure AD application ID (client ID from your app registration)
|
|
23
|
+
# * +secret+ - Your Azure AD client secret
|
|
24
|
+
# * +site_id+ - The target SharePoint site ID
|
|
25
|
+
# * +drive_id+ - The target SharePoint drive ID within the site
|
|
26
|
+
#
|
|
27
|
+
# === Configuration Sources
|
|
28
|
+
#
|
|
29
|
+
# Configuration can be provided via:
|
|
30
|
+
# * Rails credentials (config/credentials.yml.enc)
|
|
31
|
+
# * Environment variables
|
|
32
|
+
# * Direct parameter passing
|
|
33
|
+
#
|
|
34
|
+
# Example in config/storage.yml:
|
|
35
|
+
#
|
|
36
|
+
# sharepoint:
|
|
37
|
+
# ms_graph_url: <%= Rails.application.credentials.sharepoint[:ms_graph_url] %>
|
|
38
|
+
# ms_graph_version: <%= Rails.application.credentials.sharepoint[:ms_graph_version] %>
|
|
39
|
+
# auth_host: <%= Rails.application.credentials.sharepoint[:auth_host] %>
|
|
40
|
+
# tenant_id: <%= Rails.application.credentials.sharepoint[:oauth_tenant] %>
|
|
41
|
+
# app_id: <%= Rails.application.credentials.sharepoint[:oauth_app_id] %>
|
|
42
|
+
# secret: <%= Rails.application.credentials.sharepoint[:oauth_secret] %>
|
|
43
|
+
# site_id: <%= Rails.application.credentials.sharepoint[:sharepoint_site_id] %>
|
|
44
|
+
# drive_id: <%= Rails.application.credentials.sharepoint[:sharepoint_drive_id] %>
|
|
45
|
+
#
|
|
46
|
+
# === Example Usage
|
|
47
|
+
#
|
|
48
|
+
# config = M365ActiveStorage::Configuration.new(
|
|
49
|
+
# ms_graph_url: "https://graph.microsoft.com",
|
|
50
|
+
# ms_graph_version: "v1.0",
|
|
51
|
+
# auth_host: "https://login.microsoftonline.com",
|
|
52
|
+
# tenant_id: "your-tenant-id",
|
|
53
|
+
# app_id: "your-app-id",
|
|
54
|
+
# secret: "your-client-secret",
|
|
55
|
+
# site_id: "your-site-id",
|
|
56
|
+
# drive_id: "your-drive-id"
|
|
57
|
+
# )
|
|
58
|
+
#
|
|
59
|
+
# puts config.ms_graph_endpoint # => "https://graph.microsoft.com/v1.0"
|
|
60
|
+
#
|
|
61
|
+
# @attr_reader [String] ms_graph_url The Microsoft Graph API base URL
|
|
62
|
+
# @attr_reader [String] ms_graph_version The Graph API version
|
|
63
|
+
# @attr_reader [String] ms_graph_endpoint The complete Graph API endpoint (url + version)
|
|
64
|
+
# @attr_reader [String] auth_host The OAuth2 authentication host
|
|
65
|
+
# @attr_reader [String] tenant_id The Azure AD tenant ID
|
|
66
|
+
# @attr_reader [String] app_id The Azure AD application ID
|
|
67
|
+
# @attr_reader [String] secret The Azure AD client secret
|
|
68
|
+
# @attr_reader [String] site_id The SharePoint site ID
|
|
69
|
+
# @attr_reader [String] drive_id The SharePoint drive ID
|
|
70
|
+
class Configuration
|
|
71
|
+
attr_reader :ms_graph_url, :ms_graph_version, :ms_graph_endpoint,
|
|
72
|
+
:auth_host, :tenant_id,
|
|
73
|
+
:app_id, :secret, :site_id, :drive_id
|
|
74
|
+
|
|
75
|
+
# Initialize Configuration with the provided parameters
|
|
76
|
+
#
|
|
77
|
+
# All parameters are required. Missing parameters will raise a KeyError
|
|
78
|
+
# with a detailed message listing which parameters are missing.
|
|
79
|
+
#
|
|
80
|
+
# @param [Hash] options The configuration parameters
|
|
81
|
+
# @option options [String] :ms_graph_url The Microsoft Graph API base URL
|
|
82
|
+
# @option options [String] :ms_graph_version The Graph API version
|
|
83
|
+
# @option options [String] :auth_host The OAuth2 authentication host
|
|
84
|
+
# @option options [String] :tenant_id The Azure AD tenant ID
|
|
85
|
+
# @option options [String] :app_id The Azure AD application ID
|
|
86
|
+
# @option options [String] :secret The Azure AD client secret
|
|
87
|
+
# @option options [String] :site_id The SharePoint site ID
|
|
88
|
+
# @option options [String] :drive_id The SharePoint drive ID
|
|
89
|
+
#
|
|
90
|
+
# @raise [KeyError] if any required parameter is missing or empty
|
|
91
|
+
#
|
|
92
|
+
# @example
|
|
93
|
+
# config = M365ActiveStorage::Configuration.new(
|
|
94
|
+
# ms_graph_url: "https://graph.microsoft.com",
|
|
95
|
+
# ms_graph_version: "v1.0",
|
|
96
|
+
# # ... other required parameters
|
|
97
|
+
# )
|
|
98
|
+
#
|
|
99
|
+
# @see #validate_configuration!
|
|
100
|
+
def initialize(**options)
|
|
101
|
+
fetch_configuration_params(options)
|
|
102
|
+
validate_configuration!
|
|
103
|
+
rescue KeyError => e
|
|
104
|
+
raise KeyError, "Configuration error: #{e.message}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# Extract and store configuration parameters from the options hash
|
|
110
|
+
#
|
|
111
|
+
# Accepts keys in any case (string or symbol) and stores them as instance variables.
|
|
112
|
+
#
|
|
113
|
+
# @param [Hash] options The configuration options hash
|
|
114
|
+
# @return [void]
|
|
115
|
+
# @raise [KeyError] if any required parameter is missing
|
|
116
|
+
def fetch_configuration_params(options)
|
|
117
|
+
options = options.with_indifferent_access
|
|
118
|
+
@auth_host = options.fetch(:auth_host)
|
|
119
|
+
@tenant_id = options.fetch(:tenant_id)
|
|
120
|
+
@app_id = options.fetch(:app_id)
|
|
121
|
+
@secret = options.fetch(:secret)
|
|
122
|
+
@ms_graph_url = options.fetch(:ms_graph_url)
|
|
123
|
+
@ms_graph_version = options.fetch(:ms_graph_version)
|
|
124
|
+
@site_id = options.fetch(:site_id)
|
|
125
|
+
@drive_id = options.fetch(:drive_id)
|
|
126
|
+
@ms_graph_endpoint = "#{@ms_graph_url}/#{@ms_graph_version}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Validate that all required configuration parameters are present and non-empty
|
|
130
|
+
#
|
|
131
|
+
# Checks each configuration parameter and raises a detailed KeyError if any are missing
|
|
132
|
+
# or empty (nil or whitespace-only strings).
|
|
133
|
+
#
|
|
134
|
+
# @return [void]
|
|
135
|
+
# @raise [KeyError] with a detailed message listing all missing parameters
|
|
136
|
+
#
|
|
137
|
+
# @example
|
|
138
|
+
# # If tenant_id, app_id, and secret are missing:
|
|
139
|
+
# # Raises:
|
|
140
|
+
# # KeyError: SharePoint service configuration is incomplete. Missing required parameters::
|
|
141
|
+
# # - @tenant_id
|
|
142
|
+
# # - @app_id
|
|
143
|
+
# # - @secret
|
|
144
|
+
def validate_configuration!
|
|
145
|
+
missing_params = []
|
|
146
|
+
instance_variables.each do |var|
|
|
147
|
+
missing_params << var.to_s.gsub("@", "- ") unless instance_variable_get(var).present?
|
|
148
|
+
end
|
|
149
|
+
return unless missing_params.any?
|
|
150
|
+
|
|
151
|
+
missing_params.unshift("SharePoint service configuration is incomplete. Missing required parameters::")
|
|
152
|
+
raise KeyError, missing_params.join("\n")
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|