casdoor-ruby-sdk 1.0.0 → 1.1.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +134 -55
  3. data/lib/casdoor/adapter.rb +48 -0
  4. data/lib/casdoor/application.rb +48 -0
  5. data/lib/casdoor/auth.rb +143 -0
  6. data/lib/casdoor/cert.rb +47 -0
  7. data/lib/casdoor/email.rb +33 -0
  8. data/lib/casdoor/enforce.rb +47 -0
  9. data/lib/casdoor/enforcer.rb +48 -0
  10. data/lib/casdoor/entity.rb +17 -44
  11. data/lib/casdoor/global.rb +34 -0
  12. data/lib/casdoor/group.rb +48 -0
  13. data/lib/casdoor/invitation.rb +57 -0
  14. data/lib/casdoor/jwt.rb +75 -0
  15. data/lib/casdoor/ldap.rb +67 -0
  16. data/lib/casdoor/logout.rb +41 -0
  17. data/lib/casdoor/mfa.rb +53 -0
  18. data/lib/casdoor/model.rb +48 -0
  19. data/lib/casdoor/notification.rb +29 -0
  20. data/lib/casdoor/order.rb +59 -0
  21. data/lib/casdoor/order_pay.rb +40 -0
  22. data/lib/casdoor/organization.rb +47 -0
  23. data/lib/casdoor/payment.rb +61 -0
  24. data/lib/casdoor/permission.rb +57 -0
  25. data/lib/casdoor/plan.rb +48 -0
  26. data/lib/casdoor/policy.rb +67 -0
  27. data/lib/casdoor/pricing.rb +48 -0
  28. data/lib/casdoor/product.rb +48 -0
  29. data/lib/casdoor/provider.rb +48 -0
  30. data/lib/casdoor/record.rb +43 -0
  31. data/lib/casdoor/resource.rb +75 -0
  32. data/lib/casdoor/role.rb +52 -0
  33. data/lib/casdoor/session.rb +53 -0
  34. data/lib/casdoor/sms.rb +31 -0
  35. data/lib/casdoor/subscription.rb +48 -0
  36. data/lib/casdoor/syncer.rb +48 -0
  37. data/lib/casdoor/token.rb +60 -0
  38. data/lib/casdoor/transaction.rb +62 -0
  39. data/lib/casdoor/url.rb +47 -0
  40. data/lib/casdoor/user.rb +124 -0
  41. data/lib/casdoor/util.rb +147 -0
  42. data/lib/casdoor/util_modify.rb +65 -0
  43. data/lib/casdoor/version.rb +1 -1
  44. data/lib/casdoor/webhook.rb +48 -0
  45. data/lib/casdoor.rb +13 -5
  46. metadata +41 -6
  47. data/lib/casdoor/api/auth.rb +0 -153
  48. data/lib/casdoor/api/crud.rb +0 -122
  49. data/lib/casdoor/api/enforce.rb +0 -44
  50. data/lib/casdoor/api/users.rb +0 -83
  51. data/lib/casdoor/client.rb +0 -143
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0b990a9cbd0382d5af8e70e090df98adcacc9c2b4bfd79d15d16f9a40c0688a
4
- data.tar.gz: 3d09b80f3e1d36b0f582436bb647fa9e1ba297ba4a934c45b4d372585fa5465a
3
+ metadata.gz: c7442875c53f487dff6e4968e27e7aec47b23a2143799305b7c324ca32594479
4
+ data.tar.gz: 3fb25488582dc9fa39ee6ad226eab7514bc23ced81f946b3562a7e5fa9b5fb4d
5
5
  SHA512:
6
- metadata.gz: 6895b1b555053be338f78204090ecb63f9112830685905d899a868aeb010fd46e822c7438c1f29de75438803f7fd18911ce4a37aa37188b7018f21873181cfc7
7
- data.tar.gz: 803e4c91c0dfb4c325af1bc22a0300b8eca03762fe465f6934b4d513ba84bbbc5dae15940acce0133e3ebff9e0960e1cabe0ef6acc057c19e64e8ccf2fb18e93
6
+ metadata.gz: de59e3233811816621ad27f50b3d8639e780f8ce1fbf50ca9e361e466f9458eb1ec455a990991ca7ed1af6f181331c1572583cb1df658fc6bee43020b01f2e15
7
+ data.tar.gz: bc47f66464a2f7d9206e97437c8babb9aa99ea2a13e70c2798de0fdf701f1434f3ebdcb76f7985d2707872d443148dc0704ba557d1613f0775a1ec571017132e
data/README.md CHANGED
@@ -9,9 +9,13 @@
9
9
  [![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](https://discord.gg/5rPsrAzK7S)
10
10
 
11
11
  The Ruby SDK of [Casdoor](https://casdoor.ai/). It lets a Ruby application (Rails, Sinatra, Hanami, ...) sign users in
12
- with Casdoor, verify the tokens issued by Casdoor, and manage the users, applications, roles, permissions and the
12
+ with Casdoor, verify the tokens issued by Casdoor, and manage the users, applications, roles, permissions and all the
13
13
  other objects of Casdoor through its API.
14
14
 
15
+ It has the same APIs as the [Go SDK](https://github.com/casdoor/casdoor-go-sdk), in snake_case: `GetUsers()` is
16
+ `get_users`, `UpdateUserForColumns(user, columns)` is `update_user_for_columns(user, columns)`, and so on. The files
17
+ under `lib/casdoor/` match the files of the Go SDK's `casdoorsdk` package.
18
+
15
19
  ## Installation
16
20
 
17
21
  Add it to the `Gemfile`:
@@ -26,23 +30,36 @@ It requires Ruby 2.7 or later, and depends only on the [jwt](https://github.com/
26
30
 
27
31
  ## Configuration
28
32
 
33
+ | Name | Must | Description |
34
+ |-------------------|------|-------------------------------------------------------------------------------------|
35
+ | endpoint | Yes | The URL of Casdoor, such as `https://door.casdoor.com` |
36
+ | client_id | Yes | The "Client ID" of the application |
37
+ | client_secret | Yes | The "Client secret" of the application |
38
+ | certificate | No | The "Certificate" of the application's cert (PEM), only needed to verify JWT tokens |
39
+ | organization_name | Yes | The organization of the users |
40
+ | application_name | Yes | The name of the application |
41
+
42
+ Like the Go SDK, either create a client:
43
+
29
44
  ```ruby
30
45
  require 'casdoor'
31
46
 
32
- client = Casdoor::Client.new(
33
- endpoint: 'https://door.casdoor.com', # the URL of your Casdoor
34
- client_id: 'xxx', # "Client ID" of the application in Casdoor
35
- client_secret: 'xxx', # "Client secret" of the application in Casdoor
36
- certificate: File.read('cert.pem'), # "Certificate" of the application's cert, only needed to verify tokens
37
- organization_name: 'casbin', # the organization of the users
38
- application_name: 'app-example' # the application in Casdoor
39
- )
47
+ client = Casdoor.new_client(endpoint, client_id, client_secret, certificate, organization_name, application_name)
48
+ users = client.get_users
49
+ ```
50
+
51
+ or initialize the global client, then call the APIs on the `Casdoor` module:
52
+
53
+ ```ruby
54
+ Casdoor.init_config(endpoint, client_id, client_secret, certificate, organization_name, application_name)
55
+ users = Casdoor.get_users
40
56
  ```
41
57
 
42
- The certificate is the public certificate shown on the edit page of the cert that the application uses
43
- ("Certs" → the application's cert → "Certificate"). A public key in PEM format works too.
58
+ `Casdoor::Client.new` takes the same settings as keyword arguments, plus `custom_headers:` (added to every request),
59
+ `open_timeout:` and `read_timeout:`.
44
60
 
45
- `Casdoor::Client.new` also accepts `headers:` (added to every request), `open_timeout:` and `read_timeout:`.
61
+ The certificate is shown on the edit page of the cert that the application uses ("Certs" → the application's cert →
62
+ "Certificate"). A public key in PEM format works too.
46
63
 
47
64
  ## Signing users in
48
65
 
@@ -54,14 +71,13 @@ redirect_to client.get_signin_url('https://your-app.example.com/callback', state
54
71
 
55
72
  # 2. Casdoor redirects back to the callback with "code" and "state", exchange the code for the tokens
56
73
  raise 'Invalid state' unless params[:state] == session[:state]
57
- token = client.get_oauth_token(params[:code])
74
+ token = client.get_oauth_token(params[:code], params[:state])
58
75
  # => {"access_token" => "...", "id_token" => "...", "refresh_token" => "...", "expires_in" => 604800, ...}
59
76
 
60
77
  # 3. Verify the access token and read the user in it
61
78
  claims = client.parse_jwt_token(token['access_token'])
62
79
  claims.name # => "alice"
63
80
  claims.owner # => "casbin", the organization
64
- claims.email
65
81
  claims.user # => Casdoor::User with the fields of the user
66
82
  ```
67
83
 
@@ -71,20 +87,21 @@ this application (its client ID); otherwise it raises `Casdoor::InvalidTokenErro
71
87
  Other helpers:
72
88
 
73
89
  ```ruby
74
- client.get_signup_url # the sign-up page of the application
75
- client.get_user_profile_url('alice', access_token) # the profile page of a user
76
- client.get_my_profile_url(access_token) # the profile page of the signed-in user
90
+ client.get_signup_url(true, '') # the sign-up page of the application
91
+ client.get_user_profile_url('alice', access_token) # the profile page of a user
92
+ client.get_my_profile_url(access_token) # the profile page of the signed-in user
77
93
  client.refresh_oauth_token(token['refresh_token'])
78
- client.get_oauth_token_by_password('alice', 'password') # needs the "password" grant type in the application
79
- client.get_oauth_token_by_client_credentials # needs the "client_credentials" grant type
80
- client.introspect_token(token['access_token']) # => {"active" => true, ...}
81
- client.logout(token['access_token']) # signs the user out of Casdoor
94
+ client.get_oauth_token_by_password('alice', 'password') # needs the "password" grant type in the application
95
+ client.impersonate_user('alice', 'master-password') # an admin acts as a user, with the organization's master password
96
+ client.introspect_token(token['access_token']) # => {"active" => true, ...}
97
+ client.logout(token['access_token']) # signs the user out of all their sessions
98
+ client.logout_current_session(token['access_token']) # signs the user out of the current session only
82
99
  ```
83
100
 
84
101
  ## Calling the API as a user
85
102
 
86
- By default the client calls the API as the application, with the client ID and secret, which has the permissions of
87
- an admin of the application's organization. To call the API as the signed-in user instead:
103
+ By default the client calls the API as the application, with the client ID and secret. To call it as the signed-in
104
+ user instead, which is subject to the user's own permissions:
88
105
 
89
106
  ```ruby
90
107
  user_client = client.with_access_token(token['access_token'])
@@ -93,28 +110,29 @@ user.display_name = 'Alice'
93
110
  user_client.update_user(user)
94
111
  ```
95
112
 
96
- ## Managing users
113
+ ## Users
97
114
 
98
115
  ```ruby
99
116
  user = client.get_user('alice') # in the client's organization, or client.get_user('org/alice')
100
117
  user.display_name = 'Alice Smith'
101
- user.email = 'alice@example.com'
102
118
  user.properties = (user.properties || {}).merge('department' => 'R&D')
103
119
  client.update_user(user) # => true when something changed
104
-
105
- client.update_user(user, columns: %i[display_name email]) # only update these fields
120
+ client.update_user_for_columns(user, %w[displayName email]) # only update these fields
106
121
 
107
122
  client.add_user(Casdoor::User.new(name: 'bob', display_name: 'Bob', password: '123456'))
108
- client.delete_user('bob')
123
+ client.delete_user(client.get_user('bob'))
109
124
 
110
125
  client.get_users # => [Casdoor::User]
111
- users, total = client.get_pagination_users(1, 20, sort_field: 'created_time', sort_order: 'descend')
126
+ users, total = client.get_pagination_users(1, 20, field: 'name', value: 'a', sort_field: 'created_time')
127
+ client.get_sorted_users('created_time', 10)
128
+ client.get_global_users # the users of all the organizations
112
129
  client.get_user_by_email('alice@example.com')
113
130
  client.get_user_by_phone('13800000000')
114
131
  client.get_user_by_user_id('uuid') # by the "id" field of the user
115
- client.get_user_count
116
- client.set_password('casbin', 'alice', 'new-password')
117
- client.check_user_password(name: 'alice', password: 'password') # => true or false
132
+ client.update_user_by_id('casbin/alice', user) # e.g. to rename the user
133
+ client.get_user_count('') # "" for all, "1" for the online users, "0" for the offline ones
134
+ client.set_password('casbin', 'alice', 'old-password', 'new-password') # an admin can leave the old password empty
135
+ client.check_user_password(Casdoor::User.new(name: 'alice', password: 'password')) # => true or false
118
136
  ```
119
137
 
120
138
  The objects (`Casdoor::User`, `Casdoor::Application`, ...) keep all the fields returned by Casdoor, so an object that is
@@ -122,35 +140,96 @@ read, changed and updated never loses the fields this SDK doesn't know about. Th
122
140
  snake_case (`user.display_name`, `user.is_admin?`), or with their names in Casdoor (`user['displayName']`). The fields
123
141
  whose names are taken by Ruby (e.g. `hash` of a user) can only be accessed with `[]`.
124
142
 
125
- ## Managing the other objects
143
+ ## The other objects
126
144
 
127
- The same methods exist for all the objects of Casdoor: adapters, applications, certs, enforcers, groups,
128
- invitations, models, orders, organizations, payments, permissions, plans, pricings, products, providers, resources,
129
- roles, subscriptions, syncers, tokens, transactions, users and webhooks.
145
+ These methods exist for all the objects: adapters, applications, certs, enforcers, groups, invitations, models,
146
+ orders, organizations, payments, permissions, plans, pricings, products, providers, records, roles, sessions,
147
+ subscriptions, syncers, tokens, transactions, users and webhooks (the same as the Go SDK, e.g. records can't be
148
+ updated or deleted):
130
149
 
131
150
  ```ruby
132
151
  client.get_roles # => [Casdoor::Role]
133
152
  client.get_pagination_roles(1, 20) # => [[Casdoor::Role], total]
134
153
  client.get_role('admin') # => Casdoor::Role or nil
135
154
  client.add_role(Casdoor::Role.new(name: 'admin', users: ['casbin/alice']))
136
- client.update_role(role, columns: %i[users])
137
- client.delete_role('admin') # by name, or by the object
155
+ client.update_role(role)
156
+ client.update_role_for_columns(role, %w[users])
157
+ client.delete_role(role)
138
158
  ```
139
159
 
140
- Organizations, applications and tokens belong to `admin`, the other objects belong to the client's organization.
141
- A different owner can be given by `"owner/name"`, by the `owner` field of the object, or by `owner:` in the list
142
- methods. Extra query parameters of the list methods can be given in snake_case, e.g.
143
- `client.get_applications(organization: 'casbin')`.
160
+ Organizations, applications, tokens and LDAP servers belong to `admin`, the other objects belong to the client's
161
+ organization. A different owner can be given by `"owner/name"` or by the `owner` field of the object.
162
+
163
+ More APIs of the objects:
164
+
165
+ ```ruby
166
+ client.get_organization_applications # the applications of the client's organization
167
+ client.get_organization_names
168
+ client.get_global_certs
169
+ client.get_permissions_by_role('admin')
170
+ client.get_invitation_info('CODE', 'app-example')
171
+ client.get_session('alice', 'app-example')
172
+ client.get_user_orders('alice')
173
+ client.cancel_order('order-1')
174
+ client.get_user_payments('alice')
175
+ client.notify_payment(payment)
176
+ client.invoice_payment(payment)
177
+ client.get_user_transactions('alice')
178
+ affected, transaction_id = client.add_transaction(transaction)
179
+ client.add_transaction_with_dry_run(transaction, true)
180
+ ```
144
181
 
145
- ## Checking permissions
182
+ ## Permissions and policies
146
183
 
147
184
  ```ruby
148
- client.enforce(%w[alice data1 read], permission_id: 'casbin/permission-1') # => true or false
149
- client.batch_enforce([%w[alice data1 read], %w[bob data2 write]], model_id: 'casbin/model-1')
150
- # => [[true, false]], the results for each permission
185
+ # One of the permission ID, model ID, resource ID, enforcer ID or owner chooses the policies to check
186
+ client.enforce('casbin/permission-1', '', '', '', '', %w[alice data1 read]) # => true or false
187
+ client.batch_enforce('', '', '', 'casbin/enforcer-1', '', [%w[alice data1 read], %w[bob data2 write]])
188
+ # => [[true, false]], the results of each permission
189
+
190
+ rule = Casdoor::CasbinRule.new(ptype: 'p', v0: 'alice', v1: 'data1', v2: 'read')
191
+ client.add_policy(enforcer, rule)
192
+ client.update_policy(enforcer, rule, new_rule)
193
+ client.remove_policy(enforcer, rule)
194
+ client.get_policies('enforcer-1', '')
195
+ client.get_filtered_policies('casbin/enforcer-1', [Casdoor::PolicyFilter.new(ptype: 'p', field_index: 0, field_values: ['alice'])])
151
196
  ```
152
197
 
153
- One of `permission_id:`, `model_id:`, `resource_id:`, `enforcer_id:` or `owner:` chooses the policies to check.
198
+ ## Orders and payments
199
+
200
+ ```ruby
201
+ order = client.place_order([Casdoor::ProductInfo.new(name: 'product-1', quantity: 1)], 'alice')
202
+ payment = client.pay_order(order.name, 'provider_payment_dummy')
203
+ order = client.buy_product('product-1', 'provider_payment_dummy', 'alice')
204
+ ```
205
+
206
+ ## Resources, emails, SMS and notifications
207
+
208
+ ```ruby
209
+ file_url, name = client.upload_resource('alice', 'avatar', '', 'alice.png', File.binread('alice.png'))
210
+ client.get_resources('casbin', 'alice', '', '', '', '')
211
+ client.delete_resource(client.get_resource("casbin/#{name}"))
212
+
213
+ client.send_email('Title', 'Content', 'sender', 'alice@example.com', 'bob@example.com')
214
+ client.send_email_by_provider('Title', 'Content', 'sender', 'provider_email', 'alice@example.com')
215
+ client.send_sms('Your code is 123456', '+15555550100')
216
+ client.send_sms_by_provider('Your code is 123456', 'provider_sms', '+15555550100')
217
+ client.send_notification('Content', 'recipient')
218
+ ```
219
+
220
+ ## MFA and LDAP
221
+
222
+ ```ruby
223
+ response = client.initiate('casbin', 'app', 'alice') # => {"status" => "ok", "data" => {"secret" => ..., "url" => ...}}
224
+ client.verify('casbin', 'app', 'alice', secret, passcode)
225
+ client.enable('casbin', 'app', 'alice', secret, recovery_code)
226
+ client.set_preferred('casbin', 'app', 'alice', secret)
227
+ client.delete('casbin', 'alice') # disables the MFA of the user
228
+
229
+ client.get_ldaps
230
+ client.get_ldap_users('ldap-id') # => {"users" => [Casdoor::LdapUser], ...}
231
+ client.sync_ldap_users_from_server('ldap-id') # imports the LDAP users into Casdoor
232
+ ```
154
233
 
155
234
  ## Errors
156
235
 
@@ -161,14 +240,14 @@ One of `permission_id:`, `model_id:`, `resource_id:`, `enforcer_id:` or `owner:`
161
240
 
162
241
  ## Other APIs
163
242
 
164
- The SDK covers the common APIs. Any other API of Casdoor (see [the Swagger docs](https://door.casdoor.com/swagger))
165
- can be called with the authentication of the client:
243
+ Like `DoGetBytes` and `DoPost` of the Go SDK, any other API of Casdoor (see
244
+ [the Swagger docs](https://door.casdoor.com/swagger)) can be called with the authentication of the client:
166
245
 
167
246
  ```ruby
168
- client.get_data('get-organization-applications', owner: 'admin', organization: 'casbin') # GET, returns "data"
169
- client.get_response('get-sessions', owner: 'casbin') # GET, returns the whole response, e.g. "data2"
170
- client.post_json('add-ldap', {}, ldap) # POST with a JSON body
171
- client.post_form('set-password', {}, form) # POST with a form body
247
+ client.do_get_bytes(client.get_url('get-organization-applications', 'owner' => 'admin', 'organization' => 'casbin'))
248
+ client.do_get_response(client.get_url('get-sessions', 'owner' => 'casbin')) # the whole response, e.g. "data2"
249
+ client.do_post('add-ldap', nil, ldap) # POST with a JSON body
250
+ client.do_post('set-password', nil, form, true) # POST with a form body
172
251
  ```
173
252
 
174
253
  ## Development
@@ -179,8 +258,8 @@ bundle exec rubocop
179
258
  bundle exec rspec
180
259
  ```
181
260
 
182
- The integration tests run against a real Casdoor, they are skipped unless `CASDOOR_TEST_ENDPOINT` is set. Start a
183
- Casdoor with the test data, then run them:
261
+ Like the Go SDK, the tests of the APIs (`spec/<object>_spec.rb`, one for each `<object>_test.go`) run against a local
262
+ Casdoor started with `.ci/casdoor/init_data.json`; they are skipped unless `CASDOOR_TEST_ENDPOINT` is set:
184
263
 
185
264
  ```shell
186
265
  docker run -d -p 8000:8000 -e driverName=sqlite -e dataSourceName='file:casdoor.db?cache=shared' \
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2026 The Casdoor Authors. All Rights Reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Casdoor
18
+ class Adapter < Entity; end
19
+
20
+ # The adapter APIs, the counterpart of adapter.go of the Go SDK
21
+ class Client
22
+ def get_adapters
23
+ get_objects('get-adapters', Adapter, 'owner' => organization_name)
24
+ end
25
+
26
+ # Returns the objects of the page and the total count. query_map can filter and sort them, e.g.
27
+ # { field: "name", value: "alice", sort_field: "created_time", sort_order: "descend" }
28
+ def get_pagination_adapters(page, page_size, query_map = {})
29
+ get_pagination_objects('get-adapters', Adapter, organization_name, page, page_size, query_map)
30
+ end
31
+
32
+ def get_adapter(name)
33
+ get_object('get-adapter', Adapter, 'id' => get_id(name))
34
+ end
35
+
36
+ def update_adapter(adapter)
37
+ modify_object('update-adapter', Adapter, adapter, organization_name)
38
+ end
39
+
40
+ def add_adapter(adapter)
41
+ modify_object('add-adapter', Adapter, adapter, organization_name)
42
+ end
43
+
44
+ def delete_adapter(adapter)
45
+ modify_object('delete-adapter', Adapter, adapter, organization_name)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2026 The Casdoor Authors. All Rights Reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Casdoor
18
+ class Application < Entity; end
19
+
20
+ # The application APIs, the counterpart of application.go of the Go SDK
21
+ class Client
22
+ def get_applications
23
+ get_objects('get-applications', Application, 'owner' => 'admin')
24
+ end
25
+
26
+ # The applications of the client's organization
27
+ def get_organization_applications
28
+ get_objects('get-organization-applications', Application,
29
+ 'owner' => 'admin', 'organization' => organization_name)
30
+ end
31
+
32
+ def get_application(name)
33
+ get_object('get-application', Application, 'id' => get_admin_id(name))
34
+ end
35
+
36
+ def add_application(application)
37
+ modify_object('add-application', Application, application, 'admin')
38
+ end
39
+
40
+ def delete_application(application)
41
+ modify_object('delete-application', Application, application, 'admin')
42
+ end
43
+
44
+ def update_application(application)
45
+ modify_object('update-application', Application, application, 'admin')
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2026 The Casdoor Authors. All Rights Reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Casdoor
18
+ # The core configuration. The first step to use this SDK is to create a Client with it, or to initialize the global
19
+ # client with Casdoor.init_config.
20
+ AuthConfig = Struct.new(:endpoint, :client_id, :client_secret, :certificate, :organization_name,
21
+ :application_name, keyword_init: true)
22
+
23
+ # The client of the Casdoor API. By default it calls the API as the application, authenticated by the client ID
24
+ # and the client secret. Use with_access_token to call the API as a user instead.
25
+ #
26
+ # client = Casdoor::Client.new(
27
+ # endpoint: "https://door.casdoor.com",
28
+ # client_id: "...",
29
+ # client_secret: "...",
30
+ # certificate: File.read("token_jwt_key.pem"),
31
+ # organization_name: "casbin",
32
+ # application_name: "app-example"
33
+ # )
34
+ class Client
35
+ attr_reader :endpoint, :client_id, :client_secret, :certificate, :organization_name, :application_name,
36
+ :access_token
37
+ # Headers added to all the requests, e.g. to pass a gateway
38
+ attr_accessor :custom_headers, :open_timeout, :read_timeout
39
+
40
+ # certificate is the public certificate (or public key) of the application's cert in Casdoor, in PEM format.
41
+ # It's only needed by parse_jwt_token.
42
+ def initialize(endpoint:, client_id:, client_secret:, organization_name:, application_name:, certificate: nil,
43
+ custom_headers: {}, access_token: nil, open_timeout: 10, read_timeout: 30)
44
+ @endpoint = endpoint.to_s.chomp('/')
45
+ @client_id = client_id
46
+ @client_secret = client_secret
47
+ @certificate = certificate
48
+ @organization_name = organization_name
49
+ @application_name = application_name
50
+ @custom_headers = custom_headers.dup
51
+ @access_token = access_token
52
+ @open_timeout = open_timeout
53
+ @read_timeout = read_timeout
54
+ end
55
+
56
+ def auth_config
57
+ AuthConfig.new(endpoint: endpoint, client_id: client_id, client_secret: client_secret, certificate: certificate,
58
+ organization_name: organization_name, application_name: application_name)
59
+ end
60
+
61
+ # Returns a copy of the client that calls the API as the user who owns the access token, instead of as the
62
+ # application. The access token is the user's OAuth access token returned by get_oauth_token,
63
+ # refresh_oauth_token, get_oauth_token_by_password or impersonate_user. The original client keeps using the
64
+ # client ID and secret, so it's safe to create one client per user request:
65
+ #
66
+ # token = client.get_oauth_token(code, state)
67
+ # user = client.with_access_token(token["access_token"]).get_account
68
+ #
69
+ # The APIs are still subject to the permission check of Casdoor, so a normal user can only access their own data.
70
+ def with_access_token(access_token)
71
+ Client.new(endpoint: endpoint, client_id: client_id, client_secret: client_secret,
72
+ organization_name: organization_name, application_name: application_name, certificate: certificate,
73
+ custom_headers: custom_headers, access_token: access_token, open_timeout: open_timeout,
74
+ read_timeout: read_timeout)
75
+ end
76
+
77
+ # Exchanges the code that Casdoor passed to the redirect URI for the OAuth tokens. Returns the hash of the token
78
+ # API, like {"access_token" => "...", "id_token" => "...", "refresh_token" => "...", "token_type" => "Bearer",
79
+ # "expires_in" => 604800, "scope" => "read"}.
80
+ def get_oauth_token(code, _state = nil)
81
+ request_oauth_token('authorization_code', 'code' => code)
82
+ end
83
+
84
+ def refresh_oauth_token(refresh_token)
85
+ request_oauth_token('refresh_token', 'refresh_token' => refresh_token)
86
+ end
87
+
88
+ # Gets the OAuth token via the "password" grant type, i.e. the "Resource Owner Password Credentials Grant" of
89
+ # OAuth 2.0. The "password" grant type must be enabled in the application's "Grant types" in Casdoor.
90
+ # The username is the user's name inside the application's organization, like "alice" instead of "my-org/alice".
91
+ def get_oauth_token_by_password(username, password)
92
+ request_oauth_token('password', 'username' => username, 'password' => password)
93
+ end
94
+
95
+ # Gets an OAuth token which acts as the given user, so that an admin can call the APIs on behalf of the user,
96
+ # without knowing the user's own password. It's the SDK equivalent of the "Impersonation" button in Casdoor.
97
+ # The master_password is the "Master password" of the user's organization, it needs to be set in Casdoor first:
98
+ # "Organizations" -> Edit the organization -> "Master password". See: https://casdoor.ai/docs/user/impersonation
99
+ def impersonate_user(username, master_password)
100
+ get_oauth_token_by_password(username, master_password)
101
+ end
102
+
103
+ private
104
+
105
+ def request_oauth_token(grant_type, params)
106
+ form = { 'grant_type' => grant_type, 'client_id' => client_id, 'client_secret' => client_secret }.merge(params)
107
+ request = Net::HTTP::Post.new(URI(get_url('login/oauth/access_token')))
108
+ request.set_form_data(form)
109
+ token = check_oauth_response(send_request(request, auth: false))
110
+
111
+ # Older Casdoor versions put the error into the access token, like "error: invalid client_id"
112
+ if token['access_token'].to_s.start_with?('error:')
113
+ raise ApiError, token['access_token'].delete_prefix('error:').strip
114
+ end
115
+
116
+ token
117
+ end
118
+
119
+ def check_oauth_response(body)
120
+ if body['error']
121
+ message = [body['error'], body['error_description']].compact.reject(&:empty?).join(': ')
122
+ raise ApiError, message
123
+ end
124
+
125
+ check_response(body)
126
+ end
127
+ end
128
+
129
+ # The global client used by the module functions like Casdoor.get_users, see global.rb
130
+ def self.init_config(endpoint, client_id, client_secret, certificate, organization_name, application_name)
131
+ @global_client = new_client(endpoint, client_id, client_secret, certificate, organization_name, application_name)
132
+ end
133
+
134
+ def self.new_client(endpoint, client_id, client_secret, certificate, organization_name, application_name)
135
+ new_client_with_conf(AuthConfig.new(endpoint: endpoint, client_id: client_id, client_secret: client_secret,
136
+ certificate: certificate, organization_name: organization_name,
137
+ application_name: application_name))
138
+ end
139
+
140
+ def self.new_client_with_conf(config)
141
+ Client.new(**config.to_h)
142
+ end
143
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2026 The Casdoor Authors. All Rights Reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Casdoor
18
+ class Cert < Entity; end
19
+
20
+ # The cert APIs, the counterpart of cert.go of the Go SDK
21
+ class Client
22
+ # The certs of all the organizations, only allowed for global admins
23
+ def get_global_certs
24
+ get_objects('get-global-certs', Cert, nil)
25
+ end
26
+
27
+ def get_certs
28
+ get_objects('get-certs', Cert, 'owner' => organization_name)
29
+ end
30
+
31
+ def get_cert(name)
32
+ get_object('get-cert', Cert, 'id' => get_id(name))
33
+ end
34
+
35
+ def add_cert(cert)
36
+ modify_object('add-cert', Cert, cert, organization_name)
37
+ end
38
+
39
+ def update_cert(cert)
40
+ modify_object('update-cert', Cert, cert, organization_name)
41
+ end
42
+
43
+ def delete_cert(cert)
44
+ modify_object('delete-cert', Cert, cert, organization_name)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2026 The Casdoor Authors. All Rights Reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Casdoor
18
+ # Sending emails with the email provider of the application, the counterpart of email.go of the Go SDK
19
+ class Client
20
+ def send_email(title, content, sender, *receivers)
21
+ form = { 'title' => title, 'content' => content, 'sender' => sender, 'receivers' => receivers.flatten }
22
+ do_post('send-email', nil, form)
23
+ true
24
+ end
25
+
26
+ # Sends with the given email provider instead of the application's one
27
+ def send_email_by_provider(title, content, sender, provider, *receivers)
28
+ form = { 'title' => title, 'content' => content, 'sender' => sender, 'receivers' => receivers.flatten }
29
+ do_post('send-email', { 'provider' => provider }, form)
30
+ true
31
+ end
32
+ end
33
+ end