Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Password Expiration feature #535

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
55 changes: 31 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
[![Build Status](https://travis-ci.org/NoamB/sorcery.svg?branch=master)](https://travis-ci.org/NoamB/sorcery)
[![Code Climate](https://codeclimate.com/github/NoamB/sorcery.png)](https://codeclimate.com/github/NoamB/sorcery)
[![Inline docs](http://inch-ci.org/github/NoamB/sorcery.png?branch=master)](http://inch-ci.org/github/NoamB/sorcery)
# Sorcery: Magical Authentication

# sorcery

## MAINTAINER NEEDED

Currently Sorcery project is not actively maintained. I (@arnvald) try to keep looking at issues and help with any problems, but I don't develop the library anymore.
Therefore if you are interested in taking over the project, please check this issue: [https://github.com/NoamB/sorcery/issues/777](https://github.com/NoamB/sorcery/issues/777)
[![Gem Version](https://badge.fury.io/rb/sorcery.svg)](https://rubygems.org/gems/sorcery)
[![Gem Downloads](https://img.shields.io/gem/dt/sorcery.svg)](https://rubygems.org/gems/sorcery)
[![Build Status](https://travis-ci.org/Sorcery/sorcery.svg?branch=master)](https://travis-ci.org/Sorcery/sorcery)
[![Code Climate](https://codeclimate.com/github/Sorcery/sorcery.svg)](https://codeclimate.com/github/Sorcery/sorcery)
[![Inline docs](http://inch-ci.org/github/Sorcery/sorcery.svg?branch=master)](http://inch-ci.org/github/Sorcery/sorcery)
[![Join the chat at https://gitter.im/Sorcery/sorcery](https://badges.gitter.im/join_chat.svg)](https://gitter.im/Sorcery/sorcery?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

## Project

[![Join the chat at https://gitter.im/NoamB/sorcery](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NoamB/sorcery?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Magical Authentication for Rails. Supports ActiveRecord,
DataMapper, Mongoid and MongoMapper.

Inspired by restful_authentication, Authlogic and Devise. Crypto code taken
almost unchanged from Authlogic. OAuth code inspired by OmniAuth and Ryan
Bates's railscasts about it.

**Rails 5 status:** [Sorcery 0.9.1](http://rubygems.org/gems/sorcery/versions/0.9.0) should work fine with Rails 5. We can't guarantee it at this point, though, so please open an issue for any problem with Rails 5.
**Rails 5 status:** [Sorcery 0.9.1](http://rubygems.org/gems/sorcery/versions/0.9.1) should work fine with Rails 5. We can't guarantee it at this point, though, so please open an issue for any problem with Rails 5.

**Rails 4 status:** [Sorcery 0.9.0](http://rubygems.org/gems/sorcery/versions/0.9.0) is fully tested and ready for Rails 4.0, 4.1 and 4.2.

**Mongoid status:** Version 0.9.0 works with Mongoid 4.

https://github.com/NoamB/sorcery/wiki/Simple-Password-Authentication
https://github.com/Sorcery/sorcery/wiki/Simple-Password-Authentication

## Philosophy

Expand All @@ -44,10 +43,11 @@ can write your own authentication flow. It was built with a few goals in mind:
Hopefully, I've achieved this. If not, let me know.

## Useful Links

[Documentation](http://rubydoc.info/gems/sorcery) |
[Railscast](http://railscasts.com/episodes/283-authentication-with-sorcery) | [Simple tutorial](https://github.com/NoamB/sorcery/wiki/Simple-Password-Authentication) | [Example Rails 4 app](https://github.com/NoamB/sorcery-example-app)
[Railscast](http://railscasts.com/episodes/283-authentication-with-sorcery) | [Simple tutorial](https://github.com/Sorcery/sorcery/wiki/Simple-Password-Authentication) | [Example Rails 4 app](https://github.com/Sorcery/sorcery-example-app)

Check out the tutorials in the [Wiki](https://github.com/NoamB/sorcery/wiki) for more!
Check out the tutorials in the [Wiki](https://github.com/Sorcery/sorcery/wiki) for more!

## API Summary

Expand All @@ -56,6 +56,7 @@ explaining and the rest are commented:


### core

```ruby
require_login # this is a before action
login(email, password, remember_me = false)
Expand All @@ -71,18 +72,21 @@ User.authenticates_with_sorcery!
```

### http basic auth

```ruby
require_login_from_http_basic # this is a before action
```

### external

```ruby
login_at(provider) # sends the user to an external service (twitter etc.) to authenticate.
login_from(provider) # tries to login from the external provider's callback.
create_from(provider) # create the user in the local app db.
```

### remember me

```ruby
auto_login(user, should_remember=false) # login without credentials, optional remember_me
remember_me!
Expand All @@ -91,6 +95,7 @@ force_forget_me! # completely forgets all sessions by clearing the token, eve
```

### reset password

```ruby
User.load_from_reset_password_token(token)
@user.generate_reset_password_token! # if you want to send the email by youself
Expand All @@ -99,6 +104,7 @@ User.load_from_reset_password_token(token)
```

### user activation

```ruby
User.load_from_activation_token(token)
@user.setup_activation
Expand Down Expand Up @@ -186,12 +192,13 @@ end
Sidekiq and Resque integrations are coming soon.

## Single Table Inheritance (STI) Support

STI is supported via the `user.subclasses_inherit_config` setting in config/initializers/sorcery.rb.

## Full Features List by module

**Core** (see [lib/sorcery/model.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/model.rb) and
[lib/sorcery/controller.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/controller.rb)):
**Core** (see [lib/sorcery/model.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/model.rb) and
[lib/sorcery/controller.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/controller.rb)):

* login/logout, optional return user to requested url on login, configurable
redirect for non-logged-in users.
Expand All @@ -201,7 +208,7 @@ STI is supported via the `user.subclasses_inherit_config` setting in config/init
* allow multiple fields to serve as username.


**User Activation** (see [lib/sorcery/model/submodules/user_activation.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/model/submodules/user_activation.rb)):
**User Activation** (see [lib/sorcery/model/submodules/user_activation.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/model/submodules/user_activation.rb)):

* User activation by email with optional success email.
* configurable attribute names.
Expand All @@ -210,49 +217,49 @@ STI is supported via the `user.subclasses_inherit_config` setting in config/init
* Optionally prevent non-active users to login.


**Reset Password** (see [lib/sorcery/model/submodules/reset_password.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/model/submodules/reset_password.rb)):
**Reset Password** (see [lib/sorcery/model/submodules/reset_password.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/model/submodules/reset_password.rb)):

* Reset password with email verification.
* configurable mailer, method name, and attribute name.
* configurable temporary token expiration.
* configurable time between emails (hammering protection).


**Remember Me** (see [lib/sorcery/model/submodules/remember_me.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/model/submodules/remember_me.rb)):
**Remember Me** (see [lib/sorcery/model/submodules/remember_me.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/model/submodules/remember_me.rb)):

* Remember me with configurable expiration.
* configurable attribute names.
* configurable to persist globally (supporting multiple browsers at the same time), or starting anew after each login


**Session Timeout** (see [lib/sorcery/controller/submodules/session_timeout.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/controller/submodules/session_timeout.rb)):
**Session Timeout** (see [lib/sorcery/controller/submodules/session_timeout.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/controller/submodules/session_timeout.rb)):

* Configurable session timeout.
* Optionally session timeout will be calculated from last user action.


**Brute Force Protection** (see [lib/sorcery/model/submodules/brute_force_protection.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/model/submodules/brute_force_protection.rb)):
**Brute Force Protection** (see [lib/sorcery/model/submodules/brute_force_protection.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/model/submodules/brute_force_protection.rb)):

* Brute force login hammering protection.
* configurable logins before lock and lock duration.


**Basic HTTP Authentication** (see [lib/sorcery/controller/submodules/http_basic_auth.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/controller/submodules/http_basic_auth.rb)):
**Basic HTTP Authentication** (see [lib/sorcery/controller/submodules/http_basic_auth.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/controller/submodules/http_basic_auth.rb)):

* A before action for requesting authentication with HTTP Basic.
* automatic login from HTTP Basic.
* automatic login is disabled if session key changed.


**Activity Logging** (see [lib/sorcery/model/submodules/activity_logging.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/model/submodules/activity_logging.rb)):
**Activity Logging** (see [lib/sorcery/model/submodules/activity_logging.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/model/submodules/activity_logging.rb)):

* automatic logging of last login, last logout, last activity time and IP
address for last login.
* configurable timeout by which to decide whether to include a user in the
list of logged in users.


**External** (see [lib/sorcery/controller/submodules/external.rb](https://github.com/NoamB/sorcery/blob/master/lib/sorcery/controller/submodules/external.rb)):
**External** (see [lib/sorcery/controller/submodules/external.rb](https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/controller/submodules/external.rb)):

* OAuth1 and OAuth2 support (currently: Twitter, Facebook, Github, Google, Heroku,
LinkedIn, VK, LiveID, Xing, and Salesforce)
Expand Down
7 changes: 6 additions & 1 deletion lib/generators/sorcery/templates/initializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@


# -- external --
# What providers are supported by this app, i.e. [:twitter, :facebook, :github, :linkedin, :xing, :google, :liveid, :salesforce] .
# What providers are supported by this app, i.e. [:twitter, :facebook, :github, :linkedin, :xing, :google, :liveid, :salesforce, :slack] .
# Default: `[]`
#
# config.external_providers =
Expand Down Expand Up @@ -144,6 +144,11 @@
# config.vk.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=vk"
# config.vk.user_info_mapping = {:login => "domain", :name => "full_name"}
#
#config.slack.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=slack"
#config.slack.key = ''
#config.slack.secret = ''
#config.slack.user_info_mapping = {email: 'email'}
#
# To use liveid in development mode you have to replace mydomain.com with
# a valid domain even in development. To use a valid domain in development
# simply add your domain in your /etc/hosts file in front of 127.0.0.1
Expand Down
1 change: 1 addition & 0 deletions lib/sorcery/controller/submodules/external.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def self.included(base)
require 'sorcery/providers/jira'
require 'sorcery/providers/salesforce'
require 'sorcery/providers/paypal'
require 'sorcery/providers/slack'

Config.module_eval do
class << self
Expand Down
46 changes: 46 additions & 0 deletions lib/sorcery/providers/slack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Sorcery
module Providers
# This class adds support for OAuth with slack.com.

class Slack < Base

include Protocols::Oauth2

attr_accessor :auth_path, :scope, :token_url, :user_info_path

def initialize
super

@scope = 'identity.basic, identity.email'
@site = 'https://slack.com/'
@user_info_path = 'https://slack.com/api/users.identity'
@auth_path = '/oauth/authorize'
@token_url = '/api/oauth.access'
end

def get_user_hash(access_token)
response = access_token.get(user_info_path, params: { token: access_token.token })
auth_hash(access_token).tap do |h|
h[:user_info] = JSON.parse(response.body)
h[:user_info]['email'] = h[:user_info]['user']['email']
h[:uid] = h[:user_info]['user']['id']
end
end

# calculates and returns the url to which the user should be redirected,
# to get authenticated at the external provider's site.
def login_url(params, session)
authorize_url({ authorize_url: auth_path })
end

# tries to login the user from access token
def process_callback(params, session)
args = {}.tap do |a|
a[:code] = params[:code] if params[:code]
end

get_access_token(args, token_url: token_url, token_method: :post)
end
end
end
end
1 change: 0 additions & 1 deletion sorcery.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,3 @@ Gem::Specification.new do |s|
s.add_development_dependency "rspec-rails", "~> 3.1.0"
s.add_development_dependency "test-unit", "~> 3.1.0"
end

29 changes: 22 additions & 7 deletions spec/controllers/controller_oauth2_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
expect(flash[:notice]).to eq "Success!"
end

[:github, :google, :liveid, :vk, :salesforce, :paypal].each do |provider|
[:github, :google, :liveid, :vk, :salesforce, :paypal, :slack].each do |provider|

describe "with #{provider}" do

Expand Down Expand Up @@ -205,7 +205,7 @@
end

sorcery_reload!([:user_activation,:external], :user_activation_mailer => ::SorceryMailer)
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :salesforce, :paypal])
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :salesforce, :paypal, :slack])

sorcery_controller_external_property_set(:facebook, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:facebook, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
Expand All @@ -228,8 +228,13 @@
sorcery_controller_external_property_set(:paypal, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:paypal, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:paypal, :callback_url, "http://blabla.com")
sorcery_controller_external_property_set(:slack, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:slack, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:slack, :callback_url, "http://blabla.com")
end



after(:all) do
if SORCERY_ORM == :active_record
ActiveRecord::Migrator.rollback("#{Rails.root}/db/migrate/activation")
Expand Down Expand Up @@ -287,7 +292,7 @@
end
end

%w(facebook github google liveid vk salesforce).each do |provider|
%w(facebook github google liveid vk salesforce slack).each do |provider|
context "when #{provider}" do
before(:each) do
sorcery_controller_property_set(:register_login_time, true)
Expand Down Expand Up @@ -327,7 +332,7 @@

let(:user) { double('user', id: 42) }

%w(facebook github google liveid vk salesforce).each do |provider|
%w(facebook github google liveid vk salesforce slack).each do |provider|
context "when #{provider}" do
before(:each) do
sorcery_model_property_set(:authentications_class, Authentication)
Expand Down Expand Up @@ -389,7 +394,13 @@ def stub_all_oauth2_requests!
"first_name"=>"Noam",
"last_name"=>"Ben Ari"
}
]}.to_json }
],
"user"=> {
"name"=>"Sonny Whether",
"id"=>"123",
"email"=>"[email protected]"
}
}.to_json }
allow(access_token).to receive(:get) { response }
allow(access_token).to receive(:token) { "187041a618229fdaf16613e96e1caabc1e86e46bbfad228de41520e63fe45873684c365a14417289599f3" }
# access_token params for VK auth
Expand All @@ -398,7 +409,7 @@ def stub_all_oauth2_requests!
end

def set_external_property
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :salesforce, :paypal])
sorcery_controller_property_set(:external_providers, [:facebook, :github, :google, :liveid, :vk, :salesforce, :paypal, :slack])
sorcery_controller_external_property_set(:facebook, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:facebook, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:facebook, :callback_url, "http://blabla.com")
Expand All @@ -420,6 +431,9 @@ def set_external_property
sorcery_controller_external_property_set(:paypal, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:paypal, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:paypal, :callback_url, "http://blabla.com")
sorcery_controller_external_property_set(:slack, :key, "eYVNBjBDi33aa9GkA3w")
sorcery_controller_external_property_set(:slack, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
sorcery_controller_external_property_set(:slack, :callback_url, "http://blabla.com")
end

def provider_url(provider)
Expand All @@ -429,7 +443,8 @@ def provider_url(provider)
google: "https://accounts.google.com/o/oauth2/auth?client_id=#{::Sorcery::Controller::Config.google.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&state",
liveid: "https://oauth.live.com/authorize?client_id=#{::Sorcery::Controller::Config.liveid.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=wl.basic+wl.emails+wl.offline_access&state",
vk: "https://oauth.vk.com/authorize?client_id=#{::Sorcery::Controller::Config.vk.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=#{::Sorcery::Controller::Config.vk.scope}&state",
salesforce: "https://login.salesforce.com/services/oauth2/authorize?client_id=#{::Sorcery::Controller::Config.salesforce.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope#{'=' + ::Sorcery::Controller::Config.salesforce.scope unless ::Sorcery::Controller::Config.salesforce.scope.nil?}&state"
salesforce: "https://login.salesforce.com/services/oauth2/authorize?client_id=#{::Sorcery::Controller::Config.salesforce.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope#{'=' + ::Sorcery::Controller::Config.salesforce.scope unless ::Sorcery::Controller::Config.salesforce.scope.nil?}&state",
slack: "https://slack.com/oauth/authorize?client_id=#{::Sorcery::Controller::Config.slack.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=identity.basic%2C+identity.email&state"
}[provider]
end
end
20 changes: 20 additions & 0 deletions spec/rails_app/app/controllers/sorcery_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ def login_at_test_salesforce
login_at(:salesforce)
end

def login_at_test_slack
login_at(:slack)
end

def login_at_test_with_state
login_at(:facebook, {state: 'bla'})
end
Expand Down Expand Up @@ -199,6 +203,14 @@ def test_login_from_salesforce
end
end

def test_login_from_slack
if @user = login_from(:slack)
redirect_to 'bla', notice: 'Success!'
else
redirect_to 'blu', alert: 'Failed!'
end
end

def test_return_to_with_external_twitter
if @user = login_from(:twitter)
redirect_back_or_to 'bla', notice: 'Success!'
Expand Down Expand Up @@ -273,6 +285,14 @@ def test_return_to_with_external_salesforce
end
end

def test_return_to_with_external_slack
if @user = login_from(:slack)
redirect_back_or_to 'bla', notice: 'Success!'
else
redirect_to 'blu', alert: 'Failed!'
end
end

def test_create_from_provider
provider = params[:provider]
login_from(provider)
Expand Down
Loading