casdoor-ruby-sdk 1.0.0 → 1.1.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.
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
@@ -0,0 +1,52 @@
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 Role < Entity; end
19
+
20
+ # The role APIs, the counterpart of role.go of the Go SDK
21
+ class Client
22
+ def get_roles
23
+ get_objects('get-roles', Role, '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_roles(page, page_size, query_map = {})
29
+ get_pagination_objects('get-roles', Role, organization_name, page, page_size, query_map)
30
+ end
31
+
32
+ def get_role(name)
33
+ get_object('get-role', Role, 'id' => get_id(name))
34
+ end
35
+
36
+ def update_role(role)
37
+ modify_object('update-role', Role, role, organization_name)
38
+ end
39
+
40
+ def update_role_for_columns(role, columns)
41
+ modify_object('update-role', Role, role, organization_name, columns)
42
+ end
43
+
44
+ def add_role(role)
45
+ modify_object('add-role', Role, role, organization_name)
46
+ end
47
+
48
+ def delete_role(role)
49
+ modify_object('delete-role', Role, role, organization_name)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,53 @@
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 Session < Entity; end
19
+
20
+ # The session APIs, the counterpart of session.go of the Go SDK
21
+ class Client
22
+ def get_sessions
23
+ get_objects('get-sessions', Session, '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_sessions(page, page_size, query_map = {})
29
+ get_pagination_objects('get-sessions', Session, organization_name, page, page_size, query_map)
30
+ end
31
+
32
+ # A session is identified by its user and application
33
+ def get_session(name, application)
34
+ get_object('get-session', Session, 'sessionPkId' => "#{get_id(name)}/#{application}")
35
+ end
36
+
37
+ def update_session(session)
38
+ modify_object('update-session', Session, session, organization_name)
39
+ end
40
+
41
+ def update_session_for_columns(session, columns)
42
+ modify_object('update-session', Session, session, organization_name, columns)
43
+ end
44
+
45
+ def add_session(session)
46
+ modify_object('add-session', Session, session, organization_name)
47
+ end
48
+
49
+ def delete_session(session)
50
+ modify_object('delete-session', Session, session, organization_name)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
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 SMS with the SMS provider of the application, the counterpart of sms.go of the Go SDK
19
+ class Client
20
+ def send_sms(content, *receivers)
21
+ do_post('send-sms', nil, { 'content' => content, 'receivers' => receivers.flatten })
22
+ true
23
+ end
24
+
25
+ # Sends with the given SMS provider instead of the application's one
26
+ def send_sms_by_provider(content, provider, *receivers)
27
+ do_post('send-sms', { 'provider' => provider }, { 'content' => content, 'receivers' => receivers.flatten })
28
+ true
29
+ end
30
+ end
31
+ 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 Subscription < Entity; end
19
+
20
+ # The subscription APIs, the counterpart of subscription.go of the Go SDK
21
+ class Client
22
+ def get_subscriptions
23
+ get_objects('get-subscriptions', Subscription, '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_subscriptions(page, page_size, query_map = {})
29
+ get_pagination_objects('get-subscriptions', Subscription, organization_name, page, page_size, query_map)
30
+ end
31
+
32
+ def get_subscription(name)
33
+ get_object('get-subscription', Subscription, 'id' => get_id(name))
34
+ end
35
+
36
+ def update_subscription(subscription)
37
+ modify_object('update-subscription', Subscription, subscription, organization_name)
38
+ end
39
+
40
+ def add_subscription(subscription)
41
+ modify_object('add-subscription', Subscription, subscription, organization_name)
42
+ end
43
+
44
+ def delete_subscription(subscription)
45
+ modify_object('delete-subscription', Subscription, subscription, 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 Syncer < Entity; end
19
+
20
+ # The syncer APIs, the counterpart of syncer.go of the Go SDK
21
+ class Client
22
+ def get_syncers
23
+ get_objects('get-syncers', Syncer, '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_syncers(page, page_size, query_map = {})
29
+ get_pagination_objects('get-syncers', Syncer, organization_name, page, page_size, query_map)
30
+ end
31
+
32
+ def get_syncer(name)
33
+ get_object('get-syncer', Syncer, 'id' => get_id(name))
34
+ end
35
+
36
+ def update_syncer(syncer)
37
+ modify_object('update-syncer', Syncer, syncer, organization_name)
38
+ end
39
+
40
+ def add_syncer(syncer)
41
+ modify_object('add-syncer', Syncer, syncer, organization_name)
42
+ end
43
+
44
+ def delete_syncer(syncer)
45
+ modify_object('delete-syncer', Syncer, syncer, organization_name)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,60 @@
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 Token < Entity; end
19
+
20
+ # The token APIs, the counterpart of token.go of the Go SDK
21
+ class Client
22
+ def get_tokens
23
+ get_objects('get-tokens', Token, 'owner' => 'admin')
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_tokens(page, page_size, query_map = {})
29
+ get_pagination_objects('get-tokens', Token, 'admin', page, page_size, query_map)
30
+ end
31
+
32
+ def get_token(name)
33
+ get_object('get-token', Token, 'id' => get_admin_id(name))
34
+ end
35
+
36
+ def update_token(token)
37
+ modify_object('update-token', Token, token, 'admin')
38
+ end
39
+
40
+ def update_token_for_columns(token, columns)
41
+ modify_object('update-token', Token, token, 'admin', columns)
42
+ end
43
+
44
+ def add_token(token)
45
+ modify_object('add-token', Token, token, 'admin')
46
+ end
47
+
48
+ def delete_token(token)
49
+ modify_object('delete-token', Token, token, 'admin')
50
+ end
51
+
52
+ # Asks Casdoor whether the token is active, see RFC 7662. Returns a hash like
53
+ # {"active" => true, "client_id" => "...", "username" => "alice", "exp" => 1700000000, ...}.
54
+ def introspect_token(token, token_type_hint = 'access_token')
55
+ request = Net::HTTP::Post.new(URI(get_url('login/oauth/introspect')))
56
+ request.set_form_data('token' => token, 'token_type_hint' => token_type_hint)
57
+ check_oauth_response(send_request(request))
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,62 @@
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 Transaction < Entity; end
19
+
20
+ # The transaction APIs, the counterpart of transaction.go of the Go SDK
21
+ class Client
22
+ def get_transactions
23
+ get_objects('get-transactions', Transaction, '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_transactions(page, page_size, query_map = {})
29
+ get_pagination_objects('get-transactions', Transaction, organization_name, page, page_size, query_map)
30
+ end
31
+
32
+ def get_transaction(name)
33
+ get_object('get-transaction', Transaction, 'id' => get_id(name))
34
+ end
35
+
36
+ def get_user_transactions(user_name)
37
+ get_objects('get-user-transactions', Transaction, 'owner' => organization_name, 'user' => user_name)
38
+ end
39
+
40
+ def update_transaction(transaction)
41
+ modify_object('update-transaction', Transaction, transaction, organization_name)
42
+ end
43
+
44
+ # Returns [true, the ID (name) of the new transaction]
45
+ def add_transaction(transaction)
46
+ add_transaction_with_dry_run(transaction, false)
47
+ end
48
+
49
+ # With dry_run, Casdoor only validates the transaction without saving it
50
+ def add_transaction_with_dry_run(transaction, dry_run)
51
+ transaction = to_entity(Transaction, transaction)
52
+ transaction.owner = organization_name if transaction.owner.to_s.empty?
53
+ query = { 'id' => transaction.get_id }
54
+ query['dryRun'] = '1' if dry_run
55
+ [true, do_post('add-transaction', query, transaction)['data'].to_s]
56
+ end
57
+
58
+ def delete_transaction(transaction)
59
+ modify_object('delete-transaction', Transaction, transaction, organization_name)
60
+ end
61
+ end
62
+ 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
+ # The URLs of the Casdoor pages, the counterpart of url.go of the Go SDK
19
+ class Client
20
+ # The sign-up page. With enable_password, it's the plain sign-up page of the application and redirect_uri can be
21
+ # empty; otherwise the user is signed in after signing up and redirected back to redirect_uri like
22
+ # get_signin_url.
23
+ def get_signup_url(enable_password, redirect_uri)
24
+ return "#{endpoint}/signup/#{application_name}" if enable_password
25
+
26
+ get_signin_url(redirect_uri).sub('/login/oauth/authorize', '/signup/oauth/authorize')
27
+ end
28
+
29
+ # The sign-in page that the browser is redirected to. After signing in, Casdoor redirects back to redirect_uri
30
+ # with "code" and "state" in the query, and the code is exchanged with get_oauth_token.
31
+ def get_signin_url(redirect_uri, state: application_name, scope: 'read')
32
+ query = { 'client_id' => client_id, 'response_type' => 'code', 'redirect_uri' => redirect_uri,
33
+ 'scope' => scope, 'state' => state }
34
+ "#{endpoint}/login/oauth/authorize?#{URI.encode_www_form(query)}"
35
+ end
36
+
37
+ def get_user_profile_url(user_name, access_token = '')
38
+ url = "#{endpoint}/users/#{organization_name}/#{user_name}"
39
+ access_token.to_s.empty? ? url : "#{url}?#{URI.encode_www_form('access_token' => access_token)}"
40
+ end
41
+
42
+ def get_my_profile_url(access_token = '')
43
+ url = "#{endpoint}/account"
44
+ access_token.to_s.empty? ? url : "#{url}?#{URI.encode_www_form('access_token' => access_token)}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,124 @@
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 User < Entity; end
19
+
20
+ # The user APIs, the counterpart of user.go of the Go SDK
21
+ class Client
22
+ # The users of all the organizations, only allowed for global admins
23
+ def get_global_users
24
+ get_objects('get-global-users', User, nil)
25
+ end
26
+
27
+ def get_users
28
+ get_objects('get-users', User, 'owner' => organization_name)
29
+ end
30
+
31
+ # sorter is the DB column to sort by, e.g. "created_time"
32
+ def get_sorted_users(sorter, limit)
33
+ get_objects('get-sorted-users', User, 'owner' => organization_name, 'sorter' => sorter, 'limit' => limit.to_s)
34
+ end
35
+
36
+ # Returns the users of the page and the total count. query_map can filter and sort them, e.g.
37
+ # { field: "name", value: "alice", sort_field: "created_time", sort_order: "descend" }
38
+ def get_pagination_users(page, page_size, query_map = {})
39
+ get_pagination_objects('get-users', User, organization_name, page, page_size, query_map)
40
+ end
41
+
42
+ # is_online is "" to count all the users, "1" or true for the online ones, "0" or false for the offline ones
43
+ def get_user_count(is_online = '')
44
+ is_online = { true => '1', false => '0', nil => '' }.fetch(is_online, is_online)
45
+ do_get_bytes(get_url('get-user-count', 'owner' => organization_name, 'isOnline' => is_online)).to_i
46
+ end
47
+
48
+ def get_user(name)
49
+ get_object('get-user', User, 'id' => get_id(name))
50
+ end
51
+
52
+ # The user that the client is authenticated as. It's meant to be used with a client returned by
53
+ # with_access_token, so that the user of an access token can be retrieved:
54
+ #
55
+ # user = client.with_access_token(token["access_token"]).get_account
56
+ def get_account
57
+ get_object('get-account', User, nil)
58
+ end
59
+
60
+ def get_user_by_email(email)
61
+ get_object('get-user', User, 'owner' => organization_name, 'email' => email)
62
+ end
63
+
64
+ def get_user_by_phone(phone)
65
+ get_object('get-user', User, 'owner' => organization_name, 'phone' => phone)
66
+ end
67
+
68
+ # user_id is the "id" field of the user (a UUID), not "owner/name"
69
+ def get_user_by_user_id(user_id)
70
+ get_object('get-user', User, 'owner' => organization_name, 'userId' => user_id)
71
+ end
72
+
73
+ # old_password isn't required when an admin sets the password of another user, pass an empty string then
74
+ def set_password(owner, name, old_password, new_password)
75
+ form = { 'userOwner' => owner, 'userName' => name, 'oldPassword' => old_password, 'newPassword' => new_password }
76
+ do_post('set-password', nil, form, true)['status'] == 'ok'
77
+ end
78
+
79
+ # Updates the user of the ID ("owner/name"), e.g. to rename the user
80
+ def update_user_by_id(id, user)
81
+ modify_user_by_id('update-user', id, user)
82
+ end
83
+
84
+ # Updates the user of the user ID (the "id" field of the user)
85
+ def update_user_by_user_id(owner, user_id, user)
86
+ user = to_entity(User, user)
87
+ do_post('update-user', { 'owner' => owner, 'userId' => user_id }, user)['data'] == 'Affected'
88
+ end
89
+
90
+ def update_user(user)
91
+ modify_object('update-user', User, user, organization_name)
92
+ end
93
+
94
+ def update_user_for_columns(user, columns)
95
+ modify_object('update-user', User, user, organization_name, columns)
96
+ end
97
+
98
+ def add_user(user)
99
+ modify_object('add-user', User, user, organization_name)
100
+ end
101
+
102
+ def delete_user(user)
103
+ modify_object('delete-user', User, user, organization_name)
104
+ end
105
+
106
+ # Checks the password of a user, e.g. User.new(owner: "casbin", name: "alice", password: "123").
107
+ # Returns false when the password is wrong.
108
+ def check_user_password(user)
109
+ user = to_entity(User, user)
110
+ user.owner = organization_name if user.owner.to_s.empty?
111
+ do_post('check-user-password', { 'id' => user.get_id }, user)['status'] == 'ok'
112
+ rescue ApiError
113
+ false
114
+ end
115
+
116
+ private
117
+
118
+ def modify_user_by_id(action, id, user)
119
+ user = to_entity(User, user)
120
+ user.owner = organization_name if user.owner.to_s.empty?
121
+ do_post(action, { 'id' => id }, user)['data'] == 'Affected'
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,147 @@
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
+ require 'json'
18
+ require 'net/http'
19
+ require 'securerandom'
20
+ require 'time'
21
+ require 'uri'
22
+
23
+ module Casdoor
24
+ # The current time in RFC 3339, the format of the "createdTime" of Casdoor
25
+ def self.get_current_time
26
+ Time.now.iso8601
27
+ end
28
+
29
+ # The HTTP layer, the counterpart of util.go of the Go SDK. Any Casdoor API can be called with these methods,
30
+ # authenticated as the client. The responses of Casdoor are hashes like
31
+ # {"status" => "ok", "msg" => "", "data" => ..., "data2" => ...}.
32
+ class Client
33
+ def get_url(action, query_map = nil)
34
+ query = (query_map || {}).compact.transform_keys(&:to_s)
35
+ url = "#{endpoint}/api/#{action}"
36
+ query.empty? ? url : "#{url}?#{URI.encode_www_form(query)}"
37
+ end
38
+
39
+ # "owner/name" with the client's organization as the owner, unless name is already "owner/name"
40
+ def get_id(name)
41
+ get_owner_id(name, organization_name)
42
+ end
43
+
44
+ # Calls a GET API, and returns the whole response
45
+ def do_get_response(url)
46
+ body = parse_body(perform(Net::HTTP::Get.new(URI(url))))
47
+ raise ApiError, body['msg'].to_s unless body.is_a?(Hash) && body['status'] == 'ok'
48
+
49
+ body
50
+ end
51
+
52
+ # Calls a GET API, and returns the "data" of the response
53
+ def do_get_bytes(url)
54
+ do_get_response(url)['data']
55
+ end
56
+
57
+ # Calls a GET API, and returns the raw body of the response
58
+ def do_get_bytes_raw(url)
59
+ response = perform(Net::HTTP::Get.new(URI(url)))
60
+ check_response(parse_body(response)) if response['Content-Type'].to_s.include?('json')
61
+ response.body
62
+ end
63
+
64
+ # Calls a POST API, and returns the whole response. The body is posted as JSON, or as form fields when is_form,
65
+ # or as a file named "file" when is_file.
66
+ def do_post(action, query_map, post_body, is_form = false, is_file = false)
67
+ request = Net::HTTP::Post.new(URI(get_url(action, query_map)))
68
+ if is_file
69
+ request.set_form([['file', post_body.to_s, { filename: 'file' }]], 'multipart/form-data')
70
+ elsif is_form
71
+ request.set_form((post_body || {}).to_h.transform_keys(&:to_s).transform_values(&:to_s), 'multipart/form-data')
72
+ else
73
+ request['Content-Type'] = 'application/json'
74
+ request.body = post_body.nil? || post_body.is_a?(String) ? post_body.to_s : post_body.to_json
75
+ end
76
+
77
+ body = parse_body(perform(request))
78
+ raise ApiError, body['msg'].to_s unless body.is_a?(Hash) && body['status'] == 'ok'
79
+
80
+ body
81
+ end
82
+
83
+ # Calls a POST API, and returns the raw body of the response
84
+ def do_post_bytes_raw(url, content_type, body)
85
+ request = Net::HTTP::Post.new(URI(url))
86
+ request['Content-Type'] = content_type.to_s.empty? ? 'text/plain;charset=UTF-8' : content_type
87
+ request.body = body
88
+ perform(request).body
89
+ end
90
+
91
+ private
92
+
93
+ def get_owner_id(name, default_owner)
94
+ name.to_s.include?('/') ? name.to_s : "#{default_owner}/#{name}"
95
+ end
96
+
97
+ def get_admin_id(name)
98
+ get_owner_id(name, 'admin')
99
+ end
100
+
101
+ def set_auth_header(request)
102
+ if access_token
103
+ request['Authorization'] = "Bearer #{access_token}"
104
+ else
105
+ request.basic_auth(client_id.to_s, client_secret.to_s)
106
+ end
107
+ end
108
+
109
+ def perform(request, auth: true)
110
+ set_auth_header(request) if auth
111
+ request['Accept'] ||= 'application/json'
112
+ custom_headers.each { |key, value| request[key] = value }
113
+
114
+ uri = request.uri
115
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: open_timeout,
116
+ read_timeout: read_timeout) do |http|
117
+ http.request(request)
118
+ end
119
+ end
120
+
121
+ # Parses the JSON body. Errors of the HTTP layer are raised, while the {"status": "error"} responses are left to
122
+ # the caller.
123
+ def parse_body(response)
124
+ begin
125
+ body = JSON.parse(response.body.to_s)
126
+ rescue JSON::ParserError
127
+ raise HttpError.new(response.code.to_i, response.body)
128
+ end
129
+
130
+ return body if response.is_a?(Net::HTTPSuccess)
131
+ # Casdoor answers some errors with a non-2xx status and a JSON body that explains the error
132
+ return body if body.is_a?(Hash) && (body['status'] == 'error' || body['error'])
133
+
134
+ raise HttpError.new(response.code.to_i, response.body)
135
+ end
136
+
137
+ def send_request(request, auth: true)
138
+ parse_body(perform(request, auth: auth))
139
+ end
140
+
141
+ def check_response(body)
142
+ raise ApiError, body['msg'].to_s if body.is_a?(Hash) && body['status'] == 'error'
143
+
144
+ body
145
+ end
146
+ end
147
+ end