The project is in a healthy, maintained state
Ruby SDK for Casdoor: sign users in with OAuth 2.0 / OIDC, verify the JWT tokens, and manage users, organizations, applications, roles, permissions and the other objects of Casdoor.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 2.5, < 4
 Project Readme

casdoor-ruby-sdk

CI Gem Version Gem Downloads Ruby Code Style: RuboCop License Discord

The Ruby SDK of Casdoor. It lets a Ruby application (Rails, Sinatra, Hanami, ...) sign users in with Casdoor, verify the tokens issued by Casdoor, and manage the users, applications, roles, permissions and all the other objects of Casdoor through its API.

It has the same APIs as the Go SDK, in snake_case: GetUsers() is get_users, UpdateUserForColumns(user, columns) is update_user_for_columns(user, columns), and so on. The files under lib/casdoor/ match the files of the Go SDK's casdoorsdk package.

Installation

Add it to the Gemfile:

gem 'casdoor-ruby-sdk'

or install it directly with gem install casdoor-ruby-sdk.

It requires Ruby 2.7 or later, and depends only on the jwt gem.

Configuration

Name Must Description
endpoint Yes The URL of Casdoor, such as https://door.casdoor.com
client_id Yes The "Client ID" of the application
client_secret Yes The "Client secret" of the application
certificate No The "Certificate" of the application's cert (PEM), only needed to verify JWT tokens
organization_name Yes The organization of the users
application_name Yes The name of the application

Like the Go SDK, either create a client:

require 'casdoor'

client = Casdoor.new_client(endpoint, client_id, client_secret, certificate, organization_name, application_name)
users = client.get_users

or initialize the global client, then call the APIs on the Casdoor module:

Casdoor.init_config(endpoint, client_id, client_secret, certificate, organization_name, application_name)
users = Casdoor.get_users

Casdoor::Client.new takes the same settings as keyword arguments, plus custom_headers: (added to every request), open_timeout: and read_timeout:.

The certificate is shown on the edit page of the cert that the application uses ("Certs" → the application's cert → "Certificate"). A public key in PEM format works too.

Signing users in

Casdoor signs users in with the OAuth 2.0 authorization code flow:

# 1. Redirect the browser to the sign-in page of Casdoor
redirect_to client.get_signin_url('https://your-app.example.com/callback', state: session[:state] = SecureRandom.hex)

# 2. Casdoor redirects back to the callback with "code" and "state", exchange the code for the tokens
raise 'Invalid state' unless params[:state] == session[:state]
token = client.get_oauth_token(params[:code], params[:state])
# => {"access_token" => "...", "id_token" => "...", "refresh_token" => "...", "expires_in" => 604800, ...}

# 3. Verify the access token and read the user in it
claims = client.parse_jwt_token(token['access_token'])
claims.name          # => "alice"
claims.owner         # => "casbin", the organization
claims.user          # => Casdoor::User with the fields of the user

parse_jwt_token verifies the signature with the certificate, the expiration time, and that the token is issued to this application (its client ID); otherwise it raises Casdoor::InvalidTokenError.

Other helpers:

client.get_signup_url(true, '')                          # the sign-up page of the application
client.get_user_profile_url('alice', access_token)       # the profile page of a user
client.get_my_profile_url(access_token)                  # the profile page of the signed-in user
client.refresh_oauth_token(token['refresh_token'])
client.get_oauth_token_by_password('alice', 'password')   # needs the "password" grant type in the application
client.impersonate_user('alice', 'master-password')      # an admin acts as a user, with the organization's master password
client.introspect_token(token['access_token'])           # => {"active" => true, ...}
client.logout(token['access_token'])                     # signs the user out of all their sessions
client.logout_current_session(token['access_token'])     # signs the user out of the current session only

Calling the API as a user

By default the client calls the API as the application, with the client ID and secret. To call it as the signed-in user instead, which is subject to the user's own permissions:

user_client = client.with_access_token(token['access_token'])
user = user_client.get_account
user.display_name = 'Alice'
user_client.update_user(user)

Users

user = client.get_user('alice')                 # in the client's organization, or client.get_user('org/alice')
user.display_name = 'Alice Smith'
user.properties = (user.properties || {}).merge('department' => 'R&D')
client.update_user(user)                         # => true when something changed
client.update_user_for_columns(user, %w[displayName email])   # only update these fields

client.add_user(Casdoor::User.new(name: 'bob', display_name: 'Bob', password: '123456'))
client.delete_user(client.get_user('bob'))

client.get_users                                 # => [Casdoor::User]
users, total = client.get_pagination_users(1, 20, field: 'name', value: 'a', sort_field: 'created_time')
client.get_sorted_users('created_time', 10)
client.get_global_users                          # the users of all the organizations
client.get_user_by_email('alice@example.com')
client.get_user_by_phone('13800000000')
client.get_user_by_user_id('uuid')               # by the "id" field of the user
client.update_user_by_id('casbin/alice', user)   # e.g. to rename the user
client.get_user_count('')                        # "" for all, "1" for the online users, "0" for the offline ones
client.set_password('casbin', 'alice', 'old-password', 'new-password')   # an admin can leave the old password empty
client.check_user_password(Casdoor::User.new(name: 'alice', password: 'password'))   # => true or false

The objects (Casdoor::User, Casdoor::Application, ...) keep all the fields returned by Casdoor, so an object that is read, changed and updated never loses the fields this SDK doesn't know about. The fields can be read and written in snake_case (user.display_name, user.is_admin?), or with their names in Casdoor (user['displayName']). The fields whose names are taken by Ruby (e.g. hash of a user) can only be accessed with [].

The other objects

These methods exist for all the objects: adapters, applications, certs, enforcers, groups, invitations, models, orders, organizations, payments, permissions, plans, pricings, products, providers, records, roles, sessions, subscriptions, syncers, tokens, transactions, users and webhooks (the same as the Go SDK, e.g. records can't be updated or deleted):

client.get_roles                                  # => [Casdoor::Role]
client.get_pagination_roles(1, 20)                # => [[Casdoor::Role], total]
client.get_role('admin')                          # => Casdoor::Role or nil
client.add_role(Casdoor::Role.new(name: 'admin', users: ['casbin/alice']))
client.update_role(role)
client.update_role_for_columns(role, %w[users])
client.delete_role(role)

Organizations, applications, tokens and LDAP servers belong to admin, the other objects belong to the client's organization. A different owner can be given by "owner/name" or by the owner field of the object.

More APIs of the objects:

client.get_organization_applications                # the applications of the client's organization
client.get_organization_names
client.get_global_certs
client.get_permissions_by_role('admin')
client.get_invitation_info('CODE', 'app-example')
client.get_session('alice', 'app-example')
client.get_user_orders('alice')
client.cancel_order('order-1')
client.get_user_payments('alice')
client.notify_payment(payment)
client.invoice_payment(payment)
client.get_user_transactions('alice')
affected, transaction_id = client.add_transaction(transaction)
client.add_transaction_with_dry_run(transaction, true)

Permissions and policies

# One of the permission ID, model ID, resource ID, enforcer ID or owner chooses the policies to check
client.enforce('casbin/permission-1', '', '', '', '', %w[alice data1 read])   # => true or false
client.batch_enforce('', '', '', 'casbin/enforcer-1', '', [%w[alice data1 read], %w[bob data2 write]])
# => [[true, false]], the results of each permission

rule = Casdoor::CasbinRule.new(ptype: 'p', v0: 'alice', v1: 'data1', v2: 'read')
client.add_policy(enforcer, rule)
client.update_policy(enforcer, rule, new_rule)
client.remove_policy(enforcer, rule)
client.get_policies('enforcer-1', '')
client.get_filtered_policies('casbin/enforcer-1', [Casdoor::PolicyFilter.new(ptype: 'p', field_index: 0, field_values: ['alice'])])

Orders and payments

order = client.place_order([Casdoor::ProductInfo.new(name: 'product-1', quantity: 1)], 'alice')
payment = client.pay_order(order.name, 'provider_payment_dummy')
order = client.buy_product('product-1', 'provider_payment_dummy', 'alice')

Resources, emails, SMS and notifications

file_url, name = client.upload_resource('alice', 'avatar', '', 'alice.png', File.binread('alice.png'))
client.get_resources('casbin', 'alice', '', '', '', '')
client.delete_resource(client.get_resource("casbin/#{name}"))

client.send_email('Title', 'Content', 'sender', 'alice@example.com', 'bob@example.com')
client.send_email_by_provider('Title', 'Content', 'sender', 'provider_email', 'alice@example.com')
client.send_sms('Your code is 123456', '+15555550100')
client.send_sms_by_provider('Your code is 123456', 'provider_sms', '+15555550100')
client.send_notification('Content', 'recipient')

MFA and LDAP

response = client.initiate('casbin', 'app', 'alice')    # => {"status" => "ok", "data" => {"secret" => ..., "url" => ...}}
client.verify('casbin', 'app', 'alice', secret, passcode)
client.enable('casbin', 'app', 'alice', secret, recovery_code)
client.set_preferred('casbin', 'app', 'alice', secret)
client.delete('casbin', 'alice')                        # disables the MFA of the user

client.get_ldaps
client.get_ldap_users('ldap-id')                        # => {"users" => [Casdoor::LdapUser], ...}
client.sync_ldap_users_from_server('ldap-id')           # imports the LDAP users into Casdoor

Errors

  • Casdoor::ApiError: Casdoor rejected the request, e.g. "Unauthorized operation", or an OAuth error
  • Casdoor::HttpError: an unexpected HTTP status, with status and body
  • Casdoor::InvalidTokenError: parse_jwt_token can't verify the token
  • All of them inherit from Casdoor::Error.

Other APIs

Like DoGetBytes and DoPost of the Go SDK, any other API of Casdoor (see the Swagger docs) can be called with the authentication of the client:

client.do_get_bytes(client.get_url('get-organization-applications', 'owner' => 'admin', 'organization' => 'casbin'))
client.do_get_response(client.get_url('get-sessions', 'owner' => 'casbin'))   # the whole response, e.g. "data2"
client.do_post('add-ldap', nil, ldap)                   # POST with a JSON body
client.do_post('set-password', nil, form, true)         # POST with a form body

Development

bundle install
bundle exec rubocop
bundle exec rspec

Like the Go SDK, the tests of the APIs (spec/<object>_spec.rb, one for each <object>_test.go) run against a local Casdoor started with .ci/casdoor/init_data.json; they are skipped unless CASDOOR_TEST_ENDPOINT is set:

docker run -d -p 8000:8000 -e driverName=sqlite -e dataSourceName='file:casdoor.db?cache=shared' \
  -e initDataFile=/init_data.json -v "$PWD/.ci/casdoor/init_data.json:/init_data.json:ro" casbin/casdoor-all-in-one
CASDOOR_TEST_ENDPOINT=http://localhost:8000 bundle exec rspec

License

Apache-2.0