Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions examples/has_examples.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#!/usr/bin/env ruby

# Kinde Ruby SDK - Has Functionality Examples
#
# This file demonstrates the comprehensive authorization checking
# capabilities of the Kinde Ruby SDK.

require_relative '../lib/kinde_sdk'

puts "=== Kinde Ruby SDK - Has Functionality Examples ===\n\n"

# Initialize the Kinde client (example configuration)
# In a real application, these would come from environment variables
client = KindeSdk.setup do |config|
config.domain = 'https://yourapp.kinde.com'
config.client_id = 'your-client-id'
config.client_secret = 'your-client-secret'
config.redirect_url = 'http://localhost:3000/auth/callback'
end

# For these examples, we assume the user is already authenticated
# In a real app, you'd handle authentication first

begin
puts "1. Simple Role Check"
has_admin_role = client.has_roles?(['admin'])
puts "User has admin role: #{has_admin_role ? 'Yes' : 'No'}"

puts "\n2. Multiple Role Check"
has_multiple_roles = client.has_roles?(['admin', 'manager'])
puts "User has admin AND manager roles: #{has_multiple_roles ? 'Yes' : 'No'}"

puts "\n3. Permission Check"
can_edit_and_delete = client.has_permissions?(['canEdit', 'canDelete'])
puts "User can edit and delete: #{can_edit_and_delete ? 'Yes' : 'No'}"

puts "\n4. Feature Flag Check"
has_dark_mode_flags = client.has_feature_flags?([
'darkMode',
{ flag: 'theme', value: 'dark' }
])
puts "User has dark mode and theme=dark: #{has_dark_mode_flags ? 'Yes' : 'No'}"

puts "\n5. Billing Entitlements Check"
has_premium = client.has_billing_entitlements?(['premium'])
puts "User has premium entitlement: #{has_premium ? 'Yes' : 'No'}"

puts "\n6. Unified Has Check - Simple"
has_all_simple = client.has(
roles: ['admin'],
permissions: ['canEdit'],
feature_flags: ['darkMode'],
billing_entitlements: ['premium']
)
puts "User has all specified conditions (simple): #{has_all_simple ? 'Yes' : 'No'}"

puts "\n7. Unified Has Check - With Force API"
has_all_with_api = client.has(
{
roles: ['admin'],
permissions: ['canEdit']
},
true # Force API calls for fresh data
)
puts "User has admin role and canEdit permission (fresh from API): #{has_all_with_api ? 'Yes' : 'No'}"

puts "\n8. Unified Has Check - Selective Force API"
has_all_selective = client.has(
{
roles: ['admin'],
permissions: ['canEdit'],
feature_flags: ['darkMode']
},
{
roles: true, # Force API for roles
permissions: false, # Use token for permissions
feature_flags: true # Force API for feature flags
}
)
puts "User has all conditions (selective API usage): #{has_all_selective ? 'Yes' : 'No'}"

puts "\n9. Complex Condition Check - Roles with Custom Logic"
has_senior_manager = client.has_roles?([
'admin', # Simple string check
{
role: 'manager',
condition: ->(role_obj) {
# Custom logic: manager must be senior level
role_obj[:name]&.include?('Senior') || role_obj[:level] == 'senior'
}
}
])
puts "User has admin role AND is a senior manager: #{has_senior_manager ? 'Yes' : 'No'}"

puts "\n10. Complex Condition Check - Permissions with Context"
has_org_admin = client.has_permissions?([
'read:users', # Basic permission
{
permission: 'admin:users',
condition: ->(context) {
# Custom logic: admin permission only valid in admin org
context[:org_code] == 'org_admin' || context[:org_code]&.start_with?('admin_')
}
}
])
puts "User has read:users AND admin:users in admin org: #{has_org_admin ? 'Yes' : 'No'}"

puts "\n11. Complex Condition Check - Billing Entitlements with Limits"
has_high_tier_api = client.has_billing_entitlements?([
'premium', # Basic entitlement
{
entitlement: 'api-access',
condition: ->(entitlement) {
# Custom logic: API access with high usage limit
entitlement.usage_limit && entitlement.usage_limit > 1000
}
}
])
puts "User has premium AND high-tier API access: #{has_high_tier_api ? 'Yes' : 'No'}"

puts "\n12. Full Complex Example - Multiple Types with Custom Logic"
has_everything_complex = client.has(
roles: [
'admin',
{
role: 'manager',
condition: ->(role) { role[:department] == 'engineering' }
}
],
permissions: [
'read:users',
{
permission: 'deploy:production',
condition: ->(context) { context[:org_code] == 'org_engineering' }
}
],
feature_flags: [
'advanced_features',
{ flag: 'deployment_env', value: 'production' }
],
billing_entitlements: [
{
entitlement: 'enterprise',
condition: ->(ent) { ent.tier == 'enterprise' && ent.active? }
}
]
)
puts "User meets all complex authorization requirements: #{has_everything_complex ? 'Yes' : 'No'}"

puts "\n13. PHP SDK Compatible Methods"
# These methods match the PHP SDK naming convention
puts "Using PHP-style method names:"
puts "hasRoles: #{client.hasRoles(['admin']) ? 'Yes' : 'No'}"
puts "hasPermissions: #{client.hasPermissions(['canEdit']) ? 'Yes' : 'No'}"
puts "hasFeatureFlags: #{client.hasFeatureFlags(['darkMode']) ? 'Yes' : 'No'}"
puts "hasBillingEntitlements: #{client.hasBillingEntitlements(['premium']) ? 'Yes' : 'No'}"

puts "\n14. Single Item Checks"
puts "Single role check: #{client.has_roles?('admin') ? 'Yes' : 'No'}"
puts "Single permission check: #{client.has_permissions?('canEdit') ? 'Yes' : 'No'}"
puts "Single flag check: #{client.has_feature_flags?('darkMode') ? 'Yes' : 'No'}"
puts "Single entitlement check: #{client.has_billing_entitlements?('premium') ? 'Yes' : 'No'}"

puts "\n15. Edge Cases - Empty Arrays (should return true)"
puts "Empty roles check: #{client.has_roles?([]) ? 'Yes' : 'No'}"
puts "Empty permissions check: #{client.has_permissions?([]) ? 'Yes' : 'No'}"
puts "Empty flags check: #{client.has_feature_flags?([]) ? 'Yes' : 'No'}"
puts "Empty entitlements check: #{client.has_billing_entitlements?([]) ? 'Yes' : 'No'}"
puts "Empty has check: #{client.has({}) ? 'Yes' : 'No'}"

rescue StandardError => e
puts "Error during authorization checks: #{e.message}"
puts "This is expected if you're not authenticated or don't have test data"
puts "\nThe has functionality will gracefully handle errors and return false"
puts "when authentication fails or API calls encounter issues."
end

puts "\n=== Example Complete ===\n"
puts "Key Features Demonstrated:"
puts "• Simple role, permission, feature flag, and entitlement checks"
puts "• Unified has() method combining multiple authorization types"
puts "• Force API parameter for fresh data vs. token-based checks"
puts "• Complex conditions with custom logic using Ruby blocks"
puts "• Early exit optimization for performance"
puts "• Graceful error handling"
puts "• PHP and JavaScript SDK compatibility"
puts "• Support for both symbol and string keys"
puts "• Empty array handling (no constraints = allowed)"
puts ""
puts "This Ruby implementation matches the high standards and"
puts "comprehensive functionality of the JavaScript and PHP SDKs."
144 changes: 144 additions & 0 deletions lib/kinde_sdk/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,98 @@ def getEntitlementLimit(key)
# Ruby-style alias for getEntitlementLimit
alias_method :entitlement_limit, :getEntitlementLimit

# Unified method to check multiple authorization conditions
# Matches JavaScript and PHP SDK has functionality
#
# @param conditions [Hash] Hash containing roles, permissions, feature_flags, and/or billing_entitlements
# @param force_api [Boolean, Hash] Boolean to force all API calls, or hash to specify per-type
# @option force_api [Boolean] :roles Force API for roles check
# @option force_api [Boolean] :permissions Force API for permissions check
# @option force_api [Boolean] :feature_flags Force API for feature flags check
# @option force_api [Boolean] :billing_entitlements Always uses API (billing entitlements aren't in tokens)
# @return [Boolean] True if user has all specified conditions, false otherwise
# @example
# # Simple check
# client.has(
# roles: ['admin'],
# permissions: ['read:users'],
# feature_flags: ['dark_mode'],
# billing_entitlements: ['premium']
# )
# # => true
#
# # With force API settings
# client.has(
# { roles: ['admin'], permissions: ['read:users'] },
# { roles: true, permissions: false }
# )
# # => true
#
# # Complex conditions with custom logic
# client.has(
# roles: [
# 'admin',
# { role: 'manager', condition: ->(role) { role[:department] == 'engineering' } }
# ],
# permissions: [
# 'read:users',
# { permission: 'admin:users', condition: ->(ctx) { ctx[:org_code] == 'admin_org' } }
# ]
# )
# # => true
def has(conditions = {}, force_api = nil)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is ~80 lines and handles multiple responsibilities. Consider breaking out role/permission/flag/entitlement checks into private helpers (e.g., check_roles_condition) for readability and testability

return true if conditions.nil? || conditions.empty?

begin
# Parse force_api parameter
force_api_settings = parse_force_api_parameter(force_api)

# Use early exit pattern for performance (like PHP SDK)
# This avoids unnecessary API calls if any condition fails

if conditions.key?(:roles) || conditions.key?('roles')
roles = conditions[:roles] || conditions['roles']
roles_force_api = force_api_settings[:roles]
options = roles_force_api.nil? ? {} : { force_api: roles_force_api }
return false unless has_roles?(roles, options)
end

if conditions.key?(:permissions) || conditions.key?('permissions')
permissions = conditions[:permissions] || conditions['permissions']
permissions_force_api = force_api_settings[:permissions]
options = permissions_force_api.nil? ? {} : { force_api: permissions_force_api }
return false unless has_permissions?(permissions, options)
end

if conditions.key?(:feature_flags) || conditions.key?('feature_flags')
feature_flags = conditions[:feature_flags] || conditions['feature_flags']
flags_force_api = force_api_settings[:feature_flags]
options = flags_force_api.nil? ? {} : { force_api: flags_force_api }
return false unless has_feature_flags?(feature_flags, options)
end

if conditions.key?(:billing_entitlements) || conditions.key?('billing_entitlements')
billing_entitlements = conditions[:billing_entitlements] || conditions['billing_entitlements']
# Billing entitlements always use API - the options parameter is for consistency
return false unless has_billing_entitlements?(billing_entitlements, {})
end

true
rescue StandardError => e
log_error("Error in has method: #{e.message}")
false
end
Comment on lines +344 to +347

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling is good, but you might want to differentiate API errors vs. invalid arguments vs. unexpected exceptions. That way, debugging is easier in production.

end

# JavaScript SDK compatible alias
alias_method :hasConditions, :has

# PHP SDK compatible alias
alias_method :hasPermissions, :has_permissions?
alias_method :hasRoles, :has_roles?
alias_method :hasFeatureFlags, :has_feature_flags?
alias_method :hasBillingEntitlements, :has_billing_entitlements?

# Get user feature flags with pagination support.
#
# @param page_size [Integer] Number of results per page (default: 10)
Expand Down Expand Up @@ -364,6 +456,58 @@ def enhanced_user_profile

private

# Parse force_api parameter for the has method
# Matches PHP SDK parseForceApiParameter functionality
#
# @param force_api [Boolean, Hash, nil] Force API parameter
# @return [Hash] Parsed force_api settings
def parse_force_api_parameter(force_api)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice flexibility here 👏. One idea: memoize or cache results if this gets called frequently in a hot path, to avoid recomputing hash merges.

case force_api
when true
{
roles: true,
permissions: true,
feature_flags: true,
billing_entitlements: true
}
when false
{
roles: false,
permissions: false,
feature_flags: false,
billing_entitlements: true # Always true for billing entitlements
}
when Hash
{
roles: force_api[:roles] || force_api['roles'],
permissions: force_api[:permissions] || force_api['permissions'],
feature_flags: force_api[:feature_flags] || force_api['feature_flags'],
billing_entitlements: true # Always true for billing entitlements
}
else
{
roles: nil,
permissions: nil,
feature_flags: nil,
billing_entitlements: true # Always true for billing entitlements
}
end
end

# Configurable logging that works with or without Rails
# Matches the pattern used in other modules
def log_error(message)
if defined?(Rails) && Rails.logger
Rails.logger.error(message)
elsif @logger
@logger.error(message)
elsif respond_to?(:logger) && logger
logger.error(message)
else
$stderr.puts "[KindeSdk] ERROR: #{message}"
end
end

# Generic pagination helper for all paginated API calls
#
# @param data_key [String] The key in the response data containing the array of results
Expand Down
Loading