evoleap-licensing
Ruby SDK for evoleap License Manager (ELM) — license your Ruby and Rails applications using evoleap's cloud licensing system.
Supports server instance registration, per-user session licensing, component checkout/checkin, feature flags, and graceful offline handling.
Installation
Add to your Gemfile:
gem 'evoleap-licensing'
Then run:
bundle install
Quick Start
1. Get Your Credentials
Sign up at the ELM portal and create a product. You'll need:
- Product ID (GUID) — identifies your product
- Public Key (PEM file) — used to encrypt API communication
- License Key — provided to your customers
2. Configure the Gem
Create a Rails initializer at config/initializers/evoleap_licensing.rb:
EvoleapLicensing.configure do |config|
config.host = "https://elm.evoleap.com" # default; change for self-hosted/elm Lite
config.timeout = 60 # HTTP timeout in seconds
end
Store your public key securely (e.g., config/elm_public_key.pem).
3. Register the Server Instance
Do this once per deployment (e.g., in a Rake task or during setup):
public_key = File.read(Rails.root.join("config", "elm_public_key.pem"))
scm = EvoleapLicensing::ServerControlManager.new(
product_id: "your-product-guid",
version: "1.0.0",
public_key: public_key
)
identity = EvoleapLicensing::InstanceIdentity.from_hardware
result = scm.register(license_key: "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", instance_identity: identity)
if result.success?
# Persist scm.server_state.to_h as JSON (database, file, etc.)
File.write("config/server_state.json", scm.server_state.to_h.to_json)
puts "Instance registered: #{scm.server_state.instance_guid}"
else
puts "Registration failed: #{result.}"
end
4. Register Users
When a user first accesses your application:
public_key = File.read(Rails.root.join("config", "elm_public_key.pem"))
server_state = EvoleapLicensing::ServerState.new(JSON.parse(File.read("config/server_state.json")))
ucm = EvoleapLicensing::UserControlManager.new(
product_id: "your-product-guid",
version: "1.0.0",
instance_id: server_state.instance_guid,
public_key: public_key
)
user_identity = EvoleapLicensing::UserIdentity.new("uid" => current_user.id.to_s)
result = ucm.register(
user_info: { name: current_user.name, email: current_user.email },
user_identity: user_identity
)
if result.success?
# Persist ucm.user_state.to_h per user (database JSON column)
current_user.update!(elm_user_state: ucm.user_state.to_h.to_json)
end
5. Validate Sessions
On each request (via before_action or middleware):
ucm = EvoleapLicensing::UserControlManager.new(
product_id: "your-product-guid",
version: "1.0.0",
instance_id: server_state.instance_guid,
public_key: public_key,
user_state: EvoleapLicensing::UserState.new(JSON.parse(current_user.elm_user_state)),
session_state: EvoleapLicensing::SessionState.new(session[:elm_session_state])
)
user_identity = EvoleapLicensing::UserIdentity.new("uid" => current_user.id.to_s)
validity = ucm.validate_session(user_identity: user_identity)
if validity.valid?
# Save updated state
current_user.update!(elm_user_state: ucm.user_state.to_h.to_json)
session[:elm_session_state] = ucm.session_state.to_h
# Check feature flags
if ucm.session_state.features.include?("premium")
# Enable premium features
end
else
# Handle invalid license — see Error Handling below
handle_license_failure(validity)
end
Component Checkout/Checkin
Components are independently licensable features within your product (seat-based or token-based):
# Check out a component during an active session
result = ucm.check_out_components("ReportGenerator", "DataExport")
if result.success?
# Component is now checked out for this session
# result.components and result.component_entitlements have details
else
puts "Checkout failed: #{result.failure_reason}"
# :insufficient_tokens, :invalid_component, etc.
end
# Check in when done
ucm.check_in_components("ReportGenerator", "DataExport")
# Query all component statuses
status = ucm.components_status
status.components.each do |comp|
puts "#{comp['name']}: #{comp['license_model']}"
end
License Info
Get information about the license owner during an active session:
info = ucm.license_info
if info.success?
puts "Licensed to: #{info.owner_name}"
puts "Expires: #{info.expiry}"
end
State Persistence
The SDK uses three state objects that must be persisted between requests:
| State | Scope | Where to Store | Lifetime |
|---|---|---|---|
ServerState |
Per deployment | JSON file or database | Until re-registration |
UserState |
Per user | Database (JSON column) | Until re-registration |
SessionState |
Per user session | Rails session or Redis | Until session ends |
All state objects support to_h for serialization and accept a Hash in their constructor:
# Save
json = state.to_h.to_json
# Restore
state = EvoleapLicensing::ServerState.new(JSON.parse(json))
Rails Database Migration Example
class AddElmLicensingToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :elm_user_state, :jsonb, default: {}
end
end
Error Handling
Connection Failures (Server Unreachable)
When the ELM server is unreachable, the SDK uses grace periods to allow continued operation:
validity = ucm.validate_session(user_identity: user_identity)
if validity.valid?
if validity.in_validation_failure_grace_period?
# Working offline — warn the user
flash[:warning] = "License server unreachable. " \
"License valid until #{validity.grace_period_expiration.strftime('%Y-%m-%d %H:%M')}"
end
# Allow access
else
if validity.invalid_reason == :service_unreachable
# Grace period expired or never had a successful validation
render_service_unavailable
end
end
Registration Failures
result = scm.register(license_key: key, instance_identity: identity)
unless result.success?
case result.
when /Invalid license key/i
# Customer entered wrong key
flash[:error] = "The license key is invalid. Please check and try again."
when /Error contacting/i
# Network issue during registration
flash[:error] = "Cannot reach the license server. Please check your connection."
else
# Other registration errors (product disabled, activation pending, etc.)
flash[:error] = "Registration failed: #{result.}"
end
end
Validation Failures
The invalid_reason on SessionValidity and InstanceValidity tells you exactly what went wrong:
def handle_license_failure(validity)
case validity.invalid_reason
when :license_expired
redirect_to license_expired_path,
alert: "Your license has expired. Please renew to continue."
when :no_seats_available
redirect_to no_seats_path,
alert: "All licensed seats are in use. Please try again later or contact your admin."
when :session_revoked
redirect_to session_revoked_path,
alert: "Your session was revoked by an administrator."
when :instance_disabled, :user_disabled
redirect_to account_disabled_path,
alert: "Your license has been disabled. Please contact support."
when :inconsistent_registration, :inconsistent_user
# Hardware or identity changed — may need re-registration
redirect_to reregistration_path,
alert: "License identity mismatch detected. Re-registration may be required."
when :activation_pending
redirect_to activation_pending_path,
alert: "Your license is pending activation. Please check with your administrator."
when :user_tampering_detected
# System clock was manipulated
redirect_to tampering_detected_path,
alert: "A time inconsistency was detected. Please verify your system clock."
when :registration_required
redirect_to registration_path
when :service_unreachable
render_service_unavailable
else
redirect_to license_error_path,
alert: "A licensing error occurred. Please try again."
end
end
Component Checkout Failures
result = ucm.check_out_components("AdvancedReporting")
unless result.success?
case result.failure_reason
when :insufficient_tokens
flash[:error] = "Not enough tokens to use this feature."
when :invalid_component
flash[:error] = "This component is not available in your license."
else
flash[:error] = "Component checkout failed: #{result.failure_reason}"
end
end
Grace Period Configuration
Grace periods allow your application to continue working when the license server is temporarily unreachable:
strategy = EvoleapLicensing::ControlStrategy.new(
# Allow unregistered products to run for 7 days (trial period)
grace_period_for_unregistered_product: 7 * 24 * 3600,
# Automatically start a new session when the current one expires
start_session_for_expired_session: false
)
scm = EvoleapLicensing::ServerControlManager.new(
product_id: "your-product-guid",
version: "1.0.0",
public_key: public_key,
strategy: strategy
)
The validation failure grace period is set server-side when you register your product. It determines how long a previously-validated instance can operate without contacting the license server.
Example Rails Middleware
For applications that need to check licensing on every request:
# app/middleware/license_check_middleware.rb
class LicenseCheckMiddleware
SKIP_PATHS = %w[/license /health /assets].freeze
def initialize(app)
@app = app
end
def call(env)
request = ActionDispatch::Request.new(env)
# Skip licensing for certain paths
return @app.call(env) if SKIP_PATHS.any? { |p| request.path.start_with?(p) }
# Skip if no authenticated user
return @app.call(env) unless request.session[:user_id]
user = User.find_by(id: request.session[:user_id])
return @app.call(env) unless user
ucm = build_user_control_manager(user, request.session)
user_identity = EvoleapLicensing::UserIdentity.new("uid" => user.id.to_s)
validity = ucm.validate_session(user_identity: user_identity)
if validity.valid?
# Persist updated state
user.update_column(:elm_user_state, ucm.user_state.to_h.to_json)
request.session[:elm_session_state] = ucm.session_state.to_h
@app.call(env)
else
[403, { "Content-Type" => "text/html" },
["License validation failed: #{validity.invalid_reason}"]]
end
end
private
def build_user_control_manager(user, session)
public_key = Rails.application.credentials.elm_public_key
server_state = JSON.parse(File.read(Rails.root.join("config", "server_state.json")))
EvoleapLicensing::UserControlManager.new(
product_id: Rails.application.credentials.elm_product_id,
version: Rails.application.config.app_version,
instance_id: server_state["instance_guid"],
public_key: public_key,
user_state: EvoleapLicensing::UserState.new(
user.elm_user_state.is_a?(String) ? JSON.parse(user.elm_user_state) : user.elm_user_state
),
session_state: EvoleapLicensing::SessionState.new(session[:elm_session_state])
)
end
end
Add to config/application.rb:
config.middleware.use LicenseCheckMiddleware
Feature Flags
Features returned from session validation can be used to gate functionality:
# After validate_session succeeds:
features = ucm.session_state.features
if features.include?("advanced_reporting")
# Show advanced reporting UI
end
if features.include?("api_access")
# Allow API access
end
Staging vs. Production
ELM provides a free staging server for development. To switch between environments:
# config/initializers/evoleap_licensing.rb
EvoleapLicensing.configure do |config|
if Rails.env.production?
config.host = "https://elm.evoleap.com"
else
config.host = "https://staging.elm.evoleap.com"
end
end
You'll need separate product IDs and public keys for staging and production. Store them in Rails credentials:
# config/credentials.yml.enc
elm:
staging:
product_id: "staging-product-guid"
public_key: |
-----BEGIN PUBLIC KEY-----
...staging key...
-----END PUBLIC KEY-----
production:
product_id: "production-product-guid"
public_key: |
-----BEGIN PUBLIC KEY-----
...production key...
-----END PUBLIC KEY-----
API Reference
ServerControlManager
| Method | Returns | Description |
|---|---|---|
register(license_key:, instance_identity:) |
RegistrationResult |
Register server instance |
validate_instance(instance_identity:) |
InstanceValidity |
Validate instance license |
UserControlManager
| Method | Returns | Description |
|---|---|---|
register(user_info:, user_identity:) |
RegistrationResult |
Register a user |
validate_session(user_identity:, requested_duration:) |
SessionValidity |
Begin or extend session |
end_session |
Boolean |
End active session |
check_out_components(*names) |
ComponentCheckoutResult |
Check out components |
check_in_components(*names) |
ComponentCheckinResult |
Check in components |
components_status |
ComponentsStatus |
Get all component statuses |
license_info |
LicenseInfo |
Get license owner info |
Result Types
RegistrationResult—success?,error_messageSessionValidity—valid?,invalid_reason,validity_duration,in_validation_failure_grace_period?,in_unregistered_grace_period?,grace_period_expirationInstanceValidity—valid?,invalid_reason,in_validation_failure_grace_period?,in_unregistered_grace_period?,grace_period_expirationComponentCheckoutResult—success?,failure_reason,components,component_entitlementsComponentCheckinResult—success?,failure_reasonComponentsStatus—success?,components,component_entitlements,error_messageLicenseInfo—success?,owner_name,owner_logo_url,expiry,error_message
License
MIT