devise_castle 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e509c6594a298c779e9e682385bca11b4731ed22
4
+ data.tar.gz: fba122942aeaf4b8a4875284fb65a34d14e507f7
5
+ SHA512:
6
+ metadata.gz: ca5a1d49f20a03317dba378c8017b51d314bc209d49eed7b21b646782325a7661556097aa05a46df143f32bff77c836ee6b1290f9a08ea8122c9f4044c005ca5
7
+ data.tar.gz: 3683b87fbbce2fdb04a9ce8fc090686a0f055d885586f2bf36eb2d5d1dcec9d5eb22c8dcee4b322851dfb89762c009bc29a4a29b3b52ab497deb2263b40e66d3
@@ -0,0 +1,60 @@
1
+ class Devise::DeviseCastleController < DeviseController
2
+ include Devise::Controllers::Helpers
3
+
4
+ before_filter :return_not_found, except: :new
5
+
6
+ before_filter do
7
+ env['castle.skip_authorization'] = true
8
+ end
9
+
10
+ def new
11
+ challenge = castle.challenges.create
12
+ Devise.mappings.keys.flatten.any? do |scope|
13
+ redirect_to send(
14
+ "edit_#{scope}_two_factor_authentication_path", challenge.id)
15
+ end
16
+ end
17
+
18
+ def edit
19
+ @challenge = castle.challenges.find(params[:id])
20
+
21
+ # Prevent "undefined method `errors' for nil:NilClass"
22
+ self.resource = resource_class.new
23
+
24
+ render action: "#{@challenge.delivery_method}/edit"
25
+ end
26
+
27
+ def update
28
+ challenge_id = params.require(:challenge_id)
29
+ code = params.require(:code)
30
+
31
+ begin
32
+ castle.challenges.verify(challenge_id, response: code)
33
+
34
+ castle.trust_device if params[:trust_device]
35
+
36
+ Devise.mappings.keys.flatten.any? do |scope|
37
+ redirect_to after_sign_in_path_for(scope)
38
+ end
39
+ rescue Castle::Error
40
+ sign_out_with_message(:no_retries_remaining, :alert)
41
+ end
42
+ end
43
+
44
+ protected
45
+
46
+ def sign_out_with_message(message, kind = :notice)
47
+ signed_out = sign_out(resource_name)
48
+ set_flash_message kind, message if signed_out
49
+ redirect_to after_sign_out_path_for(resource_name)
50
+ end
51
+
52
+ private
53
+
54
+ def return_not_found
55
+ unless castle.mfa_in_progress?
56
+ redirect_to after_sign_in_path_for(resource_name)
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,3 @@
1
+ module Devise
2
+ class TwoFactorAuthenticationController < DeviseCastleController; end
3
+ end
@@ -0,0 +1,17 @@
1
+ class DeviseCastle::SessionsController < Devise::SessionsController
2
+ unloadable unless Rails.version =~/^4/
3
+
4
+ protected
5
+
6
+ def auth_options
7
+ # find the username
8
+ key = serialize_options(resource)[:methods].first
9
+ username = sign_in_params[key]
10
+
11
+ # find the user if any
12
+ user = resource_class.find_for_authentication(key => username)
13
+
14
+ # make it available to Warden hooks
15
+ super.merge(username: username, user: user)
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ <h2><%= t "devise.two_factor_authentication.header" %></h2>
2
+
3
+ <p><%= t "devise.two_factor_authentication.edit.authenticator.instructions" %></p>
4
+
5
+ <%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %>
6
+ <%= devise_error_messages! %>
7
+ <p><%= label_tag :code, t("devise.two_factor_authentication.edit.common.code_label") %><br />
8
+ <%= text_field_tag :code %></p>
9
+ <p><%= check_box_tag :trust_device %> <%= label_tag :trust_device %></p>
10
+ <%= hidden_field_tag(:challenge_id, @challenge.id) %>
11
+ <p><%= submit_tag t("devise.two_factor_authentication.edit.common.submit_button") %></p>
12
+ <% end -%>
13
+
14
+ <p><%= t "devise.two_factor_authentication.edit.common.recovery_message" %>
@@ -0,0 +1,17 @@
1
+ <h2><%= t "devise.two_factor_authentication.header" %></h2>
2
+
3
+ <p>
4
+ <%= t "devise.two_factor_authentication.edit.sms.instructions" %>
5
+ <%= link_to t("devise.two_factor_authentication.edit.sms.resend"), send("new_#{resource_name}_two_factor_authentication_path") %>.
6
+ </p>
7
+
8
+ <%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %>
9
+ <%= devise_error_messages! %>
10
+ <p><%= label_tag :code, t("devise.two_factor_authentication.edit.common.code_label") %><br />
11
+ <%= text_field_tag :code %></p>
12
+ <p><%= check_box_tag :trust_device %> <%= label_tag :trust_device %></p>
13
+ <%= hidden_field_tag(:challenge_id, @challenge.id) %>
14
+ <p><%= submit_tag t("devise.two_factor_authentication.edit.common.submit_button") %></p>
15
+ <% end -%>
16
+
17
+ <p><%= t "devise.two_factor_authentication.edit.common.recovery_message" %>
@@ -0,0 +1,14 @@
1
+ <h2><%= t "devise.two_factor_authentication.header" %></h2>
2
+
3
+ <p><%= t "devise.two_factor_authentication.edit.yubikey.instructions" %></p>
4
+
5
+ <%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %>
6
+ <%= devise_error_messages! %>
7
+ <p><%= label_tag :code, t("devise.two_factor_authentication.edit.common.code_label") %><br />
8
+ <%= text_field_tag :code %></p>
9
+ <p><%= check_box_tag :trust_device %> <%= label_tag :trust_device %></p>
10
+ <%= hidden_field_tag(:challenge_id, @challenge.id) %>
11
+ <p><%= submit_tag t("devise.two_factor_authentication.edit.common.submit_button") %></p>
12
+ <% end -%>
13
+
14
+ <p><%= t "devise.two_factor_authentication.edit.common.recovery_message" %>
@@ -0,0 +1,29 @@
1
+ require 'active_support/concern'
2
+ require 'devise'
3
+ require 'devise_castle/hooks'
4
+ require 'devise_castle/routes'
5
+ require 'devise_castle/hooks'
6
+ require 'devise_castle/import'
7
+ require 'devise_castle/mapping'
8
+ require 'castle'
9
+
10
+ module Devise
11
+ mattr_accessor :castle_api_secret
12
+ @@castle_api_secret = ''
13
+ end
14
+
15
+ module DeviseCastle
16
+ module Controllers
17
+ autoload :Helpers, 'devise_castle/controllers/helpers'
18
+ end
19
+ end
20
+
21
+ if defined?(Rails::Railtie)
22
+ require 'devise_castle/railtie'
23
+ Rails::Engine
24
+ end
25
+
26
+ Devise.add_module(:castle,
27
+ :controller => :two_factor_authentication,
28
+ :route => :castle,
29
+ :model => 'devise_castle/model')
@@ -0,0 +1,29 @@
1
+ module DeviseCastle
2
+ module Controllers
3
+ module Helpers
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ rescue_from Castle::UserUnauthorizedError do |error|
8
+ Devise.mappings.keys.flatten.any? do |scope|
9
+ warden.logout(scope)
10
+ throw :warden, :scope => scope, :message => :signed_out
11
+ end
12
+ end
13
+
14
+ rescue_from Castle::ChallengeRequiredError do |error|
15
+ Devise.mappings.keys.flatten.any? do |scope|
16
+ if request.format.present? and request.format.html?
17
+ session["#{scope}_return_to"] = request.path if request.get?
18
+ # todo: doesn't seem to work
19
+ redirect_to send("new_#{scope}_two_factor_authentication_path")
20
+ else
21
+ render nothing: true, status: :unauthorized
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ # Instantiate Castle client on every request
2
+ Warden::Manager.on_request do |warden|
3
+ warden.request.env['castle'] =
4
+ Castle::Client.new(warden.request, warden.cookies)
5
+ end
6
+
7
+ # Track logout.succeeded
8
+ Warden::Manager.before_logout do |record, warden, opts|
9
+ if record.respond_to?(:castle_id)
10
+ castle = warden.request.env['castle']
11
+ castle.logout
12
+ castle.track(user_id: record._castle_id, name: '$logout.succeeded')
13
+ end
14
+ end
15
+
16
+ # Track login.failed
17
+ Warden::Manager.before_failure do |env, opts|
18
+ if opts[:action] == 'unauthenticated' && opts[:username]
19
+
20
+ user_id = if opts[:user].respond_to?(:castle_id)
21
+ opts[:user]._castle_id
22
+ end
23
+
24
+ castle = env['castle']
25
+ castle.track(
26
+ name: '$login.failed',
27
+ user_id: user_id,
28
+ details: {
29
+ '$login' => opts[:username]
30
+ })
31
+ end
32
+ end
33
+
34
+ # Track login.succeeded
35
+ Warden::Manager.after_set_user :except => :fetch do |record, warden, opts|
36
+ if record.respond_to?(:castle_id)
37
+ castle = warden.request.env['castle']
38
+ castle.track(user_id: record._castle_id, name: '$login.succeeded')
39
+ castle.login(record._castle_id, email: record.email)
40
+ end
41
+ end
42
+
43
+ # Continous authentication
44
+ Warden::Manager.after_set_user do |record, warden, opts|
45
+ if record.respond_to?(:castle_id)
46
+ env = warden.request.env
47
+ castle = env['castle']
48
+ castle.authorize! unless env['castle.skip_authorization']
49
+ end
50
+ end
@@ -0,0 +1,85 @@
1
+ module DeviseCastle
2
+ class ImportError < Exception; end
3
+
4
+ class Import
5
+ attr_reader :resource_class, :batch_size
6
+
7
+ def initialize(options = {})
8
+ begin
9
+ @resource_class = eval(options[:resource_class])
10
+ rescue NameError
11
+ raise ImportError, "No such class: #{options[:resource_class]}"
12
+ end
13
+
14
+ unless supported_orm?
15
+ raise ImportError, "Only ActiveRecord and Mongoid models are supported"
16
+ end
17
+
18
+ unless Castle.config.api_secret.present?
19
+ raise ImportError, "Please add an Castle API secret to your devise.rb"
20
+ end
21
+
22
+ @batch_size = [(options[:batch_size] || 100), 100].min
23
+ end
24
+
25
+ def self.run(*args)
26
+ new(*args).run
27
+ end
28
+
29
+ def run
30
+ batches do |batch, resources|
31
+ begin
32
+ users = Castle::User.import(users: batch)
33
+ rescue Castle::Error => error
34
+ raise ImportError, error.message
35
+ end
36
+
37
+ users.zip(resources).each do |user, resource|
38
+ resource.castle_id = user.id
39
+ resource.save(validate: false)
40
+ end
41
+ end
42
+ end
43
+
44
+ def batches
45
+ if active_record?
46
+ resource_class.where("castle_id IS NULL").find_in_batches(:batch_size => batch_size) do |resources|
47
+ resources_for_wire = map_resources_to_castle_format(resources)
48
+ yield(resources_for_wire, resources) unless resources.count.zero?
49
+ end
50
+ elsif mongoid?
51
+ 0.step(resource_class.where(:castle_id => nil).count, batch_size) do |offset|
52
+ resources_for_wire = map_resources_to_castle_format(resource_class.limit(batch_size).skip(offset))
53
+ yield(resources_for_wire, resources) unless resources.count.zero?
54
+ end
55
+ end
56
+ end
57
+
58
+ def map_resources_to_castle_format(resources)
59
+ resources.map do |resource|
60
+ format = {}
61
+ format[:email] = resource.email unless resource.email.blank?
62
+ format[:id] = resource._castle_id
63
+ format[:created_at] = resource.created_at if resource.respond_to? :created_at
64
+ format
65
+ end.compact
66
+ end
67
+
68
+ def prepare_batch(batch)
69
+ { :users => batch }.to_json
70
+ end
71
+
72
+ def active_record?
73
+ (defined?(ActiveRecord::Base) && (resource_class < ActiveRecord::Base))
74
+ end
75
+
76
+ def mongoid?
77
+ (defined?(Mongoid::Document) && (resource_class < Mongoid::Document))
78
+ end
79
+
80
+ def supported_orm?
81
+ active_record? || mongoid?
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,14 @@
1
+ module DeviseCastle
2
+ module Mapping
3
+ def self.included(base)
4
+ base.alias_method_chain :default_controllers, :castle
5
+ end
6
+
7
+ private
8
+ def default_controllers_with_castle(options)
9
+ options[:controllers] ||= {}
10
+ options[:controllers][:sessions] ||= "devise_castle/sessions"
11
+ default_controllers_without_castle(options)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,52 @@
1
+ module Devise
2
+ module Models
3
+ module Castle
4
+ extend ActiveSupport::Concern
5
+
6
+ ::Castle.api_secret = Devise.castle_api_secret
7
+
8
+ included do
9
+ before_destroy :destroy_castle_user
10
+
11
+ def destroy_castle_user
12
+ castle_user_block do
13
+ ::Castle::User.destroy_existing(id)
14
+ end
15
+ end
16
+
17
+ def castle_user_block
18
+ begin
19
+ yield
20
+ rescue ::Castle::Error
21
+ true
22
+ end
23
+ end
24
+
25
+ # Override this in your Devise model to use a custom identifier
26
+ # for the Castle API:s
27
+ def castle_id
28
+ id
29
+ end
30
+
31
+ # Since the identifier will be used in API routes, it needs to be
32
+ # URI encoded
33
+ def _castle_id
34
+ URI.encode(castle_id.to_s)
35
+ end
36
+ end
37
+
38
+ # Overwrites valid_for_authentication? from Devise::Models::Authenticatable
39
+ # for verifying whether a user is allowed to sign in or not.
40
+ def valid_for_authentication?
41
+ return super unless persisted?
42
+
43
+ if super
44
+ true
45
+ else
46
+ # TODO: track unsuccessful login
47
+ false
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ module DeviseCastle
2
+ class Engine < ::Rails::Engine
3
+ ActiveSupport.on_load(:action_controller) do
4
+ include DeviseCastle::Controllers::Helpers
5
+ end
6
+
7
+ config.after_initialize do
8
+ Devise::Mapping.send :include, DeviseCastle::Mapping
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module ActionDispatch::Routing
2
+ class Mapper
3
+ protected
4
+
5
+ def devise_castle(mapping, controllers)
6
+ resources :two_factor_authentication, :only => [:new, :show, :update, :edit], :path => mapping.path_names[:two_factor_authentication], :controller => controllers[:two_factor_authentication]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module DeviseCastle
2
+ VERSION = "1.0.0".freeze
3
+ end
@@ -0,0 +1,14 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ActiveRecord
4
+ module Generators
5
+ class DeviseCastleGenerator < ActiveRecord::Generators::Base
6
+ source_root File.expand_path("../templates", __FILE__)
7
+
8
+ def copy_devise_castle_migration
9
+ migration_template "migration.rb", "db/migrate/add_castle_to_#{table_name}.rb"
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ class AddCastleTo<%= table_name.camelize %> < ActiveRecord::Migration
2
+ def up
3
+ change_table :<%= table_name %> do |t|
4
+ t.string :castle_id
5
+ end
6
+
7
+ add_index :<%= table_name %>, :castle_id, :unique => true
8
+ end
9
+
10
+ def down
11
+ remove_column :<%= table_name %>, :castle_id
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module DeviseCastle
2
+ module Generators
3
+ class DeviseCastleGenerator < Rails::Generators::NamedBase
4
+ namespace "devise_castle"
5
+
6
+ desc "Add :castle directive in the given model. Also generate migration for ActiveRecord"
7
+
8
+ def inject_devise_castle_content
9
+ path = File.join("app", "models", "#{file_path}.rb")
10
+ if File.exists?(path)
11
+ inject_into_file(path, "castle, :", :after => "devise :")
12
+ end
13
+ end
14
+
15
+ hook_for :orm
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ module DeviseCastle
2
+ module Generators
3
+ class ImportGenerator < Rails::Generators::NamedBase
4
+ desc "Import users to Castle"
5
+
6
+ def import_users_to_castle
7
+ Import.run(resource_class: class_name)
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ module DeviseCastle
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../../templates", __FILE__)
5
+ desc "Add DeviseCastle config variables to the Devise initializer"
6
+ argument :api_secret, :desc => "Your Castle API secret, which can be found on your Castle dashboard."
7
+
8
+ def add_config_options_to_initializer
9
+ devise_initializer_path = "config/initializers/devise.rb"
10
+ if File.exist?(devise_initializer_path)
11
+ old_content = File.read(devise_initializer_path)
12
+
13
+ if old_content.match(Regexp.new(/^\s*# ==> Configuration for :castle\n/))
14
+ false
15
+ else
16
+ inject_into_file(devise_initializer_path, :before => " # ==> Mailer Configuration\n") do
17
+ <<-CONTENT
18
+ # ==> Configuration for :castle
19
+ config.castle_api_secret = '#{api_secret}'
20
+
21
+ CONTENT
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def copy_locale
28
+ copy_file "../../../config/locales/en.yml", "config/locales/devise_castle.en.yml"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ require 'generators/devise/views_generator'
2
+
3
+ module DeviseCastle
4
+ module Generators
5
+ class ViewsGenerator < Rails::Generators::Base
6
+ desc 'Copies all DeviseCastle views to your application.'
7
+
8
+ argument :scope, :required => false, :default => nil,
9
+ :desc => "The scope to copy views to"
10
+
11
+ include ::Devise::Generators::ViewPathTemplates
12
+ source_root File.expand_path("../../../../app/views/devise", __FILE__)
13
+ def copy_views
14
+ view_directory :two_factor_authentication
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ require 'generators/devise/orm_helpers'
2
+
3
+ module Mongoid
4
+ module Generators
5
+ class DeviseCastleGenerator < Rails::Generators::NamedBase
6
+ end
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devise_castle
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Johan Brissmyr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: devise
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: castle-rb
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.1.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.1.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.9'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '3.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '3.1'
83
+ description: Devise extension for Castle. Secure your authentication stack with real-time
84
+ monitoring, instantly notifying you and your users on potential account hijacks.
85
+ email: johan@castle.io
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - app/controllers/devise/devise_castle_controller.rb
91
+ - app/controllers/devise/two_factor_authentication_controller.rb
92
+ - app/controllers/devise_castle/sessions_controller.rb
93
+ - app/views/devise/two_factor_authentication/authenticator/edit.html.erb
94
+ - app/views/devise/two_factor_authentication/sms/edit.html.erb
95
+ - app/views/devise/two_factor_authentication/yubikey/edit.html.erb
96
+ - lib/devise_castle.rb
97
+ - lib/devise_castle/controllers/helpers.rb
98
+ - lib/devise_castle/hooks.rb
99
+ - lib/devise_castle/import.rb
100
+ - lib/devise_castle/mapping.rb
101
+ - lib/devise_castle/model.rb
102
+ - lib/devise_castle/railtie.rb
103
+ - lib/devise_castle/routes.rb
104
+ - lib/devise_castle/version.rb
105
+ - lib/generators/active_record/devise_castle_generator.rb
106
+ - lib/generators/active_record/templates/migration.rb
107
+ - lib/generators/devise_castle/devise_castle_generator.rb
108
+ - lib/generators/devise_castle/import_generator.rb
109
+ - lib/generators/devise_castle/install_generator.rb
110
+ - lib/generators/devise_castle/views_generator.rb
111
+ - lib/generators/mongoid/devise_castle_generator.rb
112
+ homepage: https://github.com/castle/devise_castle
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 2.4.3
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: Devise extension for Castle
136
+ test_files: []