Module: Strelka::App::Auth
- Extended by:
- Configurability, Loggability, MethodUtilities, Plugin
- Includes:
- Constants
- Defined in:
- lib/strelka/app/auth.rb
Overview
Pluggable authentication and authorization for Strelka applications.
Enabling the :auth plugin by default causes all requests to your
handler to go through an authentication and authorization provider
first. This provider checks the request for the necessary credentials,
then either forwards it on if sufficient conditions are met, or
responds with the appropriate 4xx status response.
The conditions are broken down into two stages:
- Authentication -- the client is who they say they are
- Authorization -- the client is allowed to access the resources in question
Auth providers are plugins that are named Strelka::AuthProvider::
The provider for an application can be specified in the Configurability config file under the 'auth' section:
---
auth:
provider: basic
Applying Authentication
The default authentication policy is to require authentication from every request, but sometimes you may wish to narrow the restrictions a bit.
Relaxing Auth for A Few Methods
Sometimes you want to expose just one or two resources to the world, say in the
case of a REST API that includes the authentication endpoint. Obviously, clients
can't be authenticated until after they send their request that authenticates
them, so you can expose just the /login URI by using the 'no_auth_for' directive:
class MyService < Strelka::App
plugins :auth
no_auth_for '/login'
# ...
end
A String or a Regexp argument will be used to match against the request's #app_path (the path of the request URI with the Mongrel2 route omitted), and any requests which match are sent along as-is. A String will match the path exactly, with any leading or trailing '/' characters removed, and a Regexp will be tested against the #app_path as-is.
If you require some more-complex criteria for determining if the request should
skip the auth plugin, you can provide a block to no_auth_for instead.
# Allow requests from 'localhost' without auth, but require it from
# everywhere else
no_auth_for do |request|
return 'internal-user' if request.header.x_forwarded_for == '127.0.0.1'
end
If the block returns a true-ish value, it will be used in the place of the authenticated username and the request will be handed to your app.
Returning a false-ish value will go ahead with the rest of the auth processing.
You can also combine String and Regexp arguments with a block to further refine the conditions:
# Allow people to visit the seminar registration view without an account
# if there are still slots open
no_auth_for( '/register' ) do |request|
if Seminars.any? {|seminar| !seminar.full? }
'register'
else
end
end
Relaxing Auth for All But a Few Methods
Sometimes, though, you want just the opposite -- a few methods are available only to a select few, but the majority are unrestricted.
To do this, use the 'require_auth_for' directive:
class MyBlog < Strelka::App
plugins :auth
require_auth_for '/admin'
# ...
end
Note that this inverts the usual behavior of the :auth plugin: resources
will, by default, be unguarded, so be sure you keep this in mind when
using require_auth_for.
Like no_auth_for, require_auth_for can also take a block, and
a true-ish return value will cause the request to pass through the
AuthProvider.
You can't use no_auth_for and require_auth_for in the same application; doing
so will result in a ScriptError being raised when the application is loaded.
Adding Authorization
Sometimes simple authentication isn't sufficient for accessing some resources, especially if you have some kind of permissions system that dictates who can see/use what. That's where the second stage of the auth process comes into play: Authorization.
The AuthProvider you're using may provide some form of general authorization itself (especially a custom one), but typically authorization is particular to an application and even particular actions within the application.
To facilitate mapping out what actions are available to whom, there is a declaration similar to require_auth_for that can define a set of permissions that are necessary for a request to be allowed:
# The app ID, which is the default permission
ID = 'gemserver'
# GET /app/admin/upload/install would require:
# :gemserver, :admin, :upload, and :install
# permissions. What those mean is up to the AuthProvider.
require_perms_for ''
require_perms_for %r{^/admin.*}, :admin
require_perms_for %r{/upload}, :upload
require_perms_for %r{/install}, :install
and its negative corollary:
no_perms_for '/login'
Incoming requests are matched against require_perms_for patterns, and the union of all
matching permissions is gathered, then any no_auth_for patterns are used to remove permissions
from that set.
If no require_perms_for patterns are declared, authorization is not checked, unless there is at least one no_perms_for pattern, in which case all requests that don't match the negative patterns are checked (with the permission set to the ID of the app).
Authorization will be checked once authentication has succeeded. It will be called with at least
the credentials object returned from the authentication stage and the request object. Some
AuthProviders may opt to return authentication credentials as a User object of some kind (e.g.,
a database row, LDAP entry, model object, etc.), but the simpler ones just return the login of
the authenticated user. The AuthProvider may also furnish additional useful arguments such as
a database handle, permission objects, etc. to your authorization block. See the documentation
for your chosen AuthProvider for details.
Customizing Failure
As mentioned before, an authentication or authorization failure results in a 4xx status response. By default Strelka will present this back to the browser as a simple error response, but oftentimes you will want to customize it to look a little nicer, or to behave in a more-intuitive way. The easiest way to do this is to use the :errors plugin.
Redirecting to a Form
If you're using form-based session authentication (as opposed to basic auth, which has its own UI), you can rewrite the response to instruct the browser to go to a static HTML form instead using the :errors plugin:
class FormAuthApp < Strelka::App
plugins :errors, :auth, :sessions
on_status HTTP::AUTH_REQUIRED do |res, status|
formuri = res.request.uri
formuri.path = '/loginform.html'
res.reset
res.status = HTTP::SEE_OTHER
res.content_type = 'text/plain'
res.puts "This resource requires authentication."
res.header.location = formuri
return res
end
end
Responding With a Form
With the addition of the :templating plugin, you can respond with the form directly instead:
class TemplateFormAuthApp < Strelka::App
plugins :auth, :errors, :templating
layout 'examples/layout.tmpl'
templates \
form: 'examples/auth-form.tmpl',
success: 'examples/auth-success.tmpl'
on_status HTTP::AUTH_REQUIRED, :form
### Handle any (authenticated) HTTP request
def handle_request( req )
return :success
end
end
Defined Under Namespace
Modules: ClassMethods
Constant Summary collapse
- DEFAULT_AUTH_PROVIDER =
The name of the default plugin to use for authentication
:hostaccess- CONFIG_DEFAULTS =
Configuration defaults
{ provider: DEFAULT_AUTH_PROVIDER, }
Instance Attribute Summary collapse
-
#auth_provider ⇒ Object
readonly
The instance of (a subclass of) Strelka::AuthProvider that provides authentication logic for the app.
Attributes included from Plugin
Class Method Summary collapse
-
.configure(config = nil) ⇒ Object
Configurability API -- configure the Auth plugin via the 'auth' section of the unified config.
-
.included(object) ⇒ Object
Extension callback -- extend the HTTPRequest class with Auth support when this plugin is loaded.
Instance Method Summary collapse
-
#authenticate_and_authorize(request) ⇒ Object
Process authentication and authorization for the specified
request. -
#default_permission ⇒ Object
Return a permission Symbol derived from the app's ID.
-
#extended_apps ⇒ Object
The Array of apps that have had the auth plugin installed; this is used to set up the AuthProvider when the configuration loads later.
-
#handle_request(request, &block) ⇒ Object
Check authentication and authorization for requests that need it before sending them on.
-
#initialize ⇒ Object
Add an AuthProvider instance to the app.
-
#perms_required_for(request) ⇒ Object
(also: #required_perms_for)
Gather the set of permissions that apply to the specified
requestand return them. -
#provide_authentication(request) ⇒ Object
If the AuthProvider does authentication, try to extract authenticated credentials from the
requestand return them, throwing a :finish with a properly-constructed 401 (Auth required) response if that fails. -
#provide_authorization(credentials, request) ⇒ Object
Process authorization for the given
credentialsandrequest. -
#request_should_auth?(request) ⇒ Boolean
Returns
trueif the givenrequestrequires authentication.
Methods included from MethodUtilities
attr_predicate, attr_predicate_accessor, singleton_attr_accessor, singleton_attr_reader, singleton_attr_writer, singleton_method_alias, singleton_predicate_accessor, singleton_predicate_reader
Methods included from Plugin
extended, plugin_name, run_inside, run_outside
Instance Attribute Details
#auth_provider ⇒ Object (readonly)
The instance of (a subclass of) Strelka::AuthProvider that provides authentication logic for the app.
468 469 470 |
# File 'lib/strelka/app/auth.rb', line 468 def auth_provider @auth_provider end |
Class Method Details
.configure(config = nil) ⇒ Object
Configurability API -- configure the Auth plugin via the 'auth' section of the unified config.
260 261 262 263 264 265 266 267 268 269 |
# File 'lib/strelka/app/auth.rb', line 260 def self::configure( config=nil ) if config && config[:provider] self.log.debug "Setting up the %p AuthProvider for apps: %p" % [ config[:provider], self.extended_apps ] self.extended_apps.each {|app| app.auth_provider = config[:provider] } else self.log.warn "Setting up the default AuthProvider for apps %p" % [ self.extended_apps ] self.extended_apps.each {|app| app.auth_provider = DEFAULT_AUTH_PROVIDER } end end |
.included(object) ⇒ Object
Extension callback -- extend the HTTPRequest class with Auth support when this plugin is loaded.
448 449 450 451 452 |
# File 'lib/strelka/app/auth.rb', line 448 def self::included( object ) self.log.debug "Extending Request with Auth mixin" Strelka::HTTPRequest.class_eval { include Strelka::HTTPRequest::Auth } super end |
Instance Method Details
#authenticate_and_authorize(request) ⇒ Object
Process authentication and authorization for the specified request.
484 485 486 487 488 489 490 |
# File 'lib/strelka/app/auth.rb', line 484 def ( request ) credentials = nil credentials = self.provide_authentication( request ) if self.request_should_auth?( request ) request.authenticated_user = credentials self.( credentials, request ) end |
#default_permission ⇒ Object
Return a permission Symbol derived from the app's ID.
550 551 552 |
# File 'lib/strelka/app/auth.rb', line 550 def return self.app_id.downcase.gsub(/\W+/, '_' ).to_sym end |
#extended_apps ⇒ Object
The Array of apps that have had the auth plugin installed; this is used to set up the AuthProvider when the configuration loads later.
254 |
# File 'lib/strelka/app/auth.rb', line 254 singleton_attr_accessor :extended_apps |
#handle_request(request, &block) ⇒ Object
Check authentication and authorization for requests that need it before sending them on.
473 474 475 476 477 478 479 480 |
# File 'lib/strelka/app/auth.rb', line 473 def handle_request( request, &block ) self.log.debug "[:auth] Wrapping request in auth with a %p" % [ self.auth_provider ] request.auth_provider = self.auth_provider self.( request ) super end |
#initialize ⇒ Object
Add an AuthProvider instance to the app.
456 457 458 459 |
# File 'lib/strelka/app/auth.rb', line 456 def initialize( * ) super @auth_provider = self.class.auth_provider.new( self ) end |
#perms_required_for(request) ⇒ Object Also known as: required_perms_for
Gather the set of permissions that apply to the specified request and return
them.
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 |
# File 'lib/strelka/app/auth.rb', line 557 def perms_required_for( request ) self.log.debug "Gathering required perms for: %s %s" % [ request.verb, request.app_path ] # Return the empty set if any negative auth criteria match return [] if self.negative_perms_criteria_match?( request ) # If there aren't any positive criteria, default to requiring authorization with # the app's ID as the permission if self.class.positive_perms_criteria.empty? return [ self. ] end # Apply positive auth criteria return self.union_positive_perms_criteria( request ) end |
#provide_authentication(request) ⇒ Object
If the AuthProvider does authentication, try to extract authenticated credentials
from the request and return them, throwing a :finish with
a properly-constructed 401 (Auth required) response if that fails.
496 497 498 499 500 501 |
# File 'lib/strelka/app/auth.rb', line 496 def provide_authentication( request ) provider = self.auth_provider self.log.info "Authenticating request using provider: %p" % [ provider ] credentials = provider.authenticate( request ) or finish_with( HTTP::AUTH_REQUIRED, "Authentication required." ) return credentials end |
#provide_authorization(credentials, request) ⇒ Object
Process authorization for the given credentials and request.
The credentials argument is the opaque return value from a valid authentication, or
nil if the request didn't require authentication.
507 508 509 510 511 512 |
# File 'lib/strelka/app/auth.rb', line 507 def ( credentials, request ) provider = self.auth_provider perms = self.perms_required_for( request ) self.log.debug "Perms required: %p" % [ perms ] provider.( credentials, request, perms ) unless perms.empty? end |
#request_should_auth?(request) ⇒ Boolean
Returns true if the given request requires authentication.
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 |
# File 'lib/strelka/app/auth.rb', line 516 def request_should_auth?( request ) self.log.debug "Checking to see if Auth(entication/orization) should be applied for app_path: %p" % [ request.app_path ] # If there are positive criteria, return true if the request matches any of them, # or false if they don't if self.class.has_positive_auth_criteria? criteria = self.class.positive_auth_criteria self.log.debug " checking %d positive auth criteria" % [ criteria.length ] return criteria.any? do |pattern, block| self.request_matches_criteria( request, pattern, &block ) end return false # If there are negative criteria, return false if the request matches any of them, # or true if they don't elsif self.class.has_negative_auth_criteria? criteria = self.class.negative_auth_criteria self.log.debug " checking %d negative auth criteria" % [ criteria.length ] return false if criteria.any? do |pattern, block| rval = self.request_matches_criteria( request, pattern, &block ) self.log.debug " matched: %p -> %p" % [ pattern, block ] if rval rval end return true else self.log.debug " no auth criteria; default to requiring auth" return true end end |