Rails: using other login with Enki blogging gem - ruby-on-rails

I'm trying to set up a blog with Rails and really like Enki www.Enkiblog.com. However, I'm a bit confused about the authentication system it uses with Open Id. On development, it allows you to bypass authentication, but for production it seems to require you to use an OpenId server
I'm hoping to incorporate a simpler authentication system with it, but don't know if that's simpler (I'm a noob) or if it's better to try to figure out how to set up an OpenId server (which requires more installation)
I did look at an Open Id server called Masquerade but it totally confused me. I'm not sure if it's something I try to incorporate with Enki (like a Rails Engine) or if it's a totally separate application.
Any thoughts how I can simplify the authentication, or how it can be simplified so that a noob can use it?
This is the enki.yml file that sets up for OpenId authentication in Enki
# Configuration options for your blog - customise to taste
# This file contains no secret information, so can be stored in source control (unlike database.yml)
title: My Enki Blog
url: http://enkiblog.com
author:
name: Don Alias # For copyright notice and ATOM feeds
email: don#enkiblog.com # Exception emails will go here, and it is used in ATOM feeds
open_id: # These are used to login to the admin area
- http://enkiblog.com
- http://secondaryopenid.com
# Delete the following section if your site will not be acting as an OpenID delegate (http://wiki.openid.net/Delegation)
# If you're deploying with mongrel, make sure you read http://rhnh.net/2008/04/13/nginx-openid-delegation-and-yadis
open_id_delegation:
server: http://www.myopenid.com/server
delegate: http://username.myopenid.com
This is the create action from admin/sessions controller that does that authentication
def create
return successful_login if allow_login_bypass? && params[:bypass_login]
if params[:openid_url].blank? && !request.env[Rack::OpenID::RESPONSE]
flash.now[:error] = "You must provide an OpenID URL"
render :action => 'new'
else
authenticate_with_open_id(params[:openid_url]) do |result, identity_url|
if result.successful?
if enki_config.author_open_ids.include?(URI.parse(identity_url))
return successful_login
else
flash.now[:error] = "You are not authorized"
end
else
flash.now[:error] = result.message
end
render :action => 'new'
end
end
end

All you need is an open id at google (your profile url) or any of the open id providers at openid.net. Once you have found your open id url, all you need to do is open config/enki.yml and change the open-id value to your open id url. e.g.:
open_id: # These are used to login to the admin area
- https://plus.google.com/102381073183096542549
Now to login at admin panel, just enter your profile url. It may ask you to login at your open id provider's site like google.com and then ask for permissions for yourblog.com. Once granted, you will have the admin access. Nobody else will be able to access admin area since you have restricted it to the open_id in enki.yml

You can actually rip out the OpenID Authentication out of Enki by hacking the code. I managed to do this (Rails newbie) so it isn't all that difficult.
The only problem with this being that now you are on your own for updating the Enki code... From what I remember there were quite a few changes that had to be made to accomplish this.

Related

How to fix Brakeman redirect issue with multiple rest endpoints

I'm currently working on a solution for doing redirects in RoR because I got an error within the brakeman report saying that I have to fix redirects in a proper way.
I understand what the message says and how to solve it within one controller action.
But now I got the following. During the instantiation of the new method I set the HTTP_REFERER header which can be used in the create action.
This is giving me a Brakeman warning which can be found on the following link
Suppose I got the following controller with multiple endpoints:
def new
#my_model_set = MyModel.new
#referer = request.env['HTTP_REFERER'] # We want to redirect to this referer after a create
end
def create
...
if #my_model_set.save
flash_message :success, t('notification.item_created', type: #my_model_set.model_name.human)
if params[:referer].present?
redirect_to params[:referer]
else
redirect_to admin_my_model_set_path
end
else
...
end
end
I already tried to fix this by using the redirect_back method from RoR but that's using the referer link of the create method which I don't want to use.
if #my_model_set.save
flash_message :success, t('notification.item_created', type: #my_model_set.model_name.human)
redirect_back(fallback_location: admin_my_model_set_path)
else
...
end
The main problem in your code is that params[:referer] can be set by your user (or an attacker forging a link for your user) to an arbitrary value by appending ?referer=https://malicious.site to the url. You will then redirect to that, which is an open redirect vulnerability.
You could also argue that the referer header is technically user input, and you will be redirecting to it, but I would say in most cases and modern browsers that would probably be an acceptable risk, because an attacker does not really have a way to exploit it (but it might depend on the exact circumstances).
One solution that immediately comes to mind for similar cases would be the session - but on the one hand this is a rest api if I understand correctly, so there is no session, and on the other hand, it would still not be secure against an attacker linking to your #new endpoint from a malicious domain.
I think you should validate the domain before you redirect to it. If there is a common pattern (like for example if all of these are subdomains of yourdomain.com), validate for that. Or you could have your users register their domains first before you redirect to it (see how OAuth2 works for example, you have to register your app domain first before the user can get redirected there with a token).
If your user might just come from anywhere to #new and you want to send them back wherever they came from - that I think is not a good requirement, you should probably not do that, or you should carefully assess the risk and consciously accept it if you want to for some reason. In most cases there is a more secure solution.

Writing Cucumber tests that pass extra information

I have a Ruby on Rails program with feature tests in Cucumber.
I just implemented a feature where an admin can create a new password for a client-user. Now, on the "edit client" page, there's an additional button that allows the admin to set the password. Now, I just need to make a cucumber test.
I am trying to base this off of the normal test for client changing password, and the test for admin changing the user's information. What I have is this:
Feature: Analyst changes client's password
As an Analyst
I want to change client's password
So that I can reset the client's account
Background:
Given the following client accounts
| email | password |
| user1#someorg.com | password |
And I am logged in as an admin
#javascript
Scenario: Update a Client user
Given I navigate to the Clients Management Page
When I edit the Client User "user1#someorg.com"
And I click on "button"
Then I should be on the Clients Password Page
#javascript
Scenario: Can change password if confirmation matches
Given I navigate to the Clients Password Page
And I enter "Password1" as the password
And I enter "Password1" as the password confirmation
And I submit the form
Then I should be taken to the Client Landing Page
And The client's password should be "Password1"
In the steps, I have:
Given /^I navigate to the Clients Password Page$/ do
client_management_index_page = ClientsPasswordPage.new Capybara.current_session
client_management_index_page.visit
end
Then /^I should be on the Clients Password Page$/ do
client_password_page = ClientsPasswordPage.new Capybara.current_session
expect(client_password_page).to be_current_page
end
and ClientsPaswordPage:
class ClientsPasswordPage
include PageMixin
include Rails.application.routes.url_helpers
def initialize session
initialize_page session, edit_admin_client_password_path
end
end
except that edit_admin_client_password_path takes an :id, for the user who's being edited. I can't figure out how to get that information into it.
In case it matters, I'm using Devise for the security stuff...
There are a few ways to do this. The simplest is to realize that you're only creating one client during the test so
Client.first # whatever class represents clients
will always be that client. Obviously that doesn't work if you have tests where you create one more than client, so then you can create instance variables in your cucumber steps which get set on the World and can then be accessed from other steps and passed to your page objects
When I edit the Client User "user1#someorg.com"
#current_client = Client.find_by(email: "user1#someorg.com") # obviously would actually be a parameter to the step
...
end
Then /^I should be on the Clients Password Page$/ do
client_password_page = ClientsPasswordPage.new Capybara.current_session, #current_client
expect(client_password_page).to be_current_page
end
of course without the page object overhead this would just become
Then /^I should be on the Clients Password Page$/ do
expect(page).to have_current_path(edit_admin_client_password_path(#current_client))
end
There are a number of things you can do to simplify this scenario. If you have simpler scenarios, with simpler step definitions then it will be easier to solve implementation problems like how you get a client in one step to be available in a second step.
The main way to simplify scenarios is to not have anything at all in the scenario that explains HOW you have implemented the functionality. If you take all the clicking on buttons, filling in fields, and visiting pages out of your scenarios you can focus on the business problem.
So how about
Background
Given there is a client
And I am logged in as an admin
Scenario: Change clients password
When I change the clients password
Then the client should have a new password
Note: This immediately raises the question 'How does the client find out about there new password?', which is what good simple scenarios do, they make you ask valuable questions. Answering this is probably out of scope here.
Now lets have a look at the implementation.
Given 'there is a client' do
#client = create_new_client
end
When 'I change the clients password' do
visit admin_change_password_path(#client)
change_client_password(client: #client)
end
Just this might be sufficient to get you on the right path. In addition something like
Given 'I am logged in as an admin' do
#i = create_admin_user
login_as(user: #i)
end
would help.
What we have done here is
Push the HOW down your stack so that now the code you right to make this work is out of your scenarios and step definitions
Used variable to communicate between steps the line #client = create_new_client creates a global (actually global to Cucumber::World) variable that is available in all step definitions
You can create helper methods by adding modules to Cucumber world and defining methods in them. Note these methods are global so you have to think carefully about names (there are very good reasons why these methods are global). So
module UserStepHelper
def create_new_client
...
end
def create_admin_user
...
end
def change_client_password(client: )
...
end
end
World UserStepHelper
Will create a helper method you can use in any of your step definitions.
You can see an example of this approach here. A project I used for a talk at CukeUp 2013. Perhaps you could use this as your tutorial example.

How does web session work in this rails project?

Introduction:
I am building a facebook app which auto wishes happy birthday. I am building it in Rails and using a ruby API wrapper called fb_graph. The creator of fb_graph has graciously provided a working sample application fb_graph_sample
After playing around with it, I do not understand how the sessions/cookies work. For example, check out this code:
def require_authentication
authenticate Facebook.find_by_id(session[:current_user])
rescue Unauthorized => e
redirect_to root_url and return false
end
def authenticate(user)
raise Unauthorized unless user
session[:current_user] = user.id
end
Where does session[:current_user] comes from?
Under config/initializers/session_store.rb,
FbGraphSample::Application.config.session_store :cookie_store, :key => '_fb_graph_sample_session'
So, I look at the cookies for localhost which is where I am deploying it as using Chrome inspector tools, I see _fb_graph_sample_session with value, domain, path, expires, size, http, etc...
I still don't see how session[:current_user] comes about? Looking at the development.sqlite3 file, there is only 1 data for the facebook model. The id is 1 so, that leads me to believe that [:current_user] is 1 and the code is calling 'authenticate Facebook.find_by_id(1)'
Can someone please explain how session[:current_user] translate to 1? I read railstutorial.org chapter on signing-in-out and it creates a sessions controller but there is no sessions controller in the fb_graph_sample app.
Thanks,
I get's set in the authenticate method:
session[:current_user] = user.id
The app is using cookie based session store, when a user logs in a cookie (think of it as a special hash) is written to his browser. You use session pretty much as a hash, you can set as shown above, or get, i.e.
<%= "logged in as #{User.find(session[:current_user]).name}" %>

Digest authentication in Devise

I'm using Rails 3 and Devise for authentication. I have a proper working devise for the website and basic authentication for API (json handler). How do I enable the digest authentication?
Their Wiki is telling me to add
def http_authenticate
authenticate_or_request_with_http_digest do |user_name, password|
user_name == "foo" && password == "bar"
end
warden.custom_failure! if performed?
end
Where do I add it to and how do I make user_name/password match?
That wiki entry sure assumes a lot.
My best guess is you need to add it to the appropriate controller (or the Application controller if you want it for everything).
And then add a :before_filter :http_authenticate!
You could also try tracking down the person who wrote that wiki page and asking them.
Note. This relies on Warden to perform your authentication - Devise only handles accounts.
One of the reasons this stuff isn't documented so well is most people use a sophisticated authentication management system (eg. OmniAuth), and something else for permissions/authorization eg. DeclarativeAuthorization or CanCan if you prefer something more light weight.
HTTPBasic (and I assume Digest) tends not to play nicely with these.

Prevent Authlogic from establishing a session/cookie for non-HTML requests

I'm using Authlogic and Rails 3. On top of the regular browser-based user experience (logging in via form and whatnot), I'd like to implement an API.
Authlogic seems to support single access tokens that don't persist by default. I supply them by adding a GET argument as in:
/users.xml?user_credentails=my_single_access_token
Question: Is there any way I can have Authlogic accept the API key via HTTP Basic Auth? Highrise does something just like this, allowing for:
curl -u 605b32dd:X http://sample.highrisehq.com/people/1.xml
The same with Freshbooks:
curl -u insert_token_here:X https://sample.freshbooks.com/api/2.1/xml-in -d '[xml body here]'
How I would go about imitating this functionality? I can't even figure out where the input data (postdata from forms, HTTP basic, API token) are taken in. I've boiled it down to a call to UserSessions.find with no arguments, but I lose track of it after there.
Any help would be much appreciated!
Related question: I'd also like to disable session persistence (make it so that no cookie is stored) if HTTP basic is used. Any help on this too would be appreciated!
If you're implementing an API, you could consider building a separate Rack application that is then mounted at '/api/1.0/...' and shares your models.
That way you are not tying yourself into having your API directly related to your public routes, which could be difficult to construct for the API user.
A good approach would be to create a simple Sinatra application that exposes just the methods that you want, and to then create a separate authentication strategy:
require 'sinatra'
require 'active_support' # all the Rails stuff
require 'lib/user' # your User class
require 'sinatra/respond_to' # gem install sinatra-respond_to
Sinatra::Application.register Sinatra::RespondTo
use Rack::Auth::Basic, "API", do |username, password|
User.find_by_login(username).valid_password?(password)
end
get '/api/1.0/posts' do
#posts = Post.recent # assuming you have a Post model...
respond_to do |wants|
wants.xml { #posts.to_xml }
wants.to_json { #posts.to_json }
end
end
get '/api/1.0/users/:id' do
#user = User.find_by_login(params[:id])
# Careful here - don't release personal details!
respond_to do |wants|
wants.xml { #user.to_xml }
wants.to_json { #user.to_json }
end
end
Versioning your API with a '1.0' (or similar) in the path means that if you change your models you can create a new version of your API without breaking your users' existing code.
Using this you should be able to allow users to authenticate with HTTP Basic in the form:
curl -u steven:password http://example.com/api/1.0/users/steven.xml
curl -u steven:password http://example.com/api/1.0/users/steven.json
curl -u steven:password http://example.com/api/1.0/posts.xml
To get this running, save it as 'api.rb', and either run it as a Rack Middleware, or create a 'config.ru' file like so:
require 'api'
run Sinatra::Application
And then from that directory:
rackup
Disclaimer: I'm not a 100% this is possible in the way your describing without hacking up Authlogic's core functionality.
The first issue your going to have is that authlogic prevents the use of SSO tokens for authentication unless the request is ATOM or RSS to override this you need to pass a config paramater see here: http://rdoc.info/github/binarylogic/authlogic/master/Authlogic/Session/Params/Config
To the core issue: I don't see any 'easy' way to handle this functionality, however what you could do for something like curl is pass the user token as a paramater (using the -G option) just like you would when visiting the url.
cURL Documentation: http://curl.haxx.se/docs/manpage.html
Forgive me if I misunderstand your question, but I think the answer is a simple "no." You're mixing two metaphors here. If you want a secure API key, use the single access token; if you want to use http basic access authentication, you need a different base64 glyph -- and http basic auth isn't particularly secure (unless used over https, which isn't generally practical).
In more detail:
Per the wikipedia, http basic authentication is intended to provide a username and password in a simple, standard, but fairly insecure base64 encoded glyph.
To use basic auth, then I believe you want to generate the glyph via a simple
Base64.encode64("#{user.name}:#{password}")
...and I'd probably do this by having the user type their password, since you can't derive the password from the crypted_password that authlogic stores in your database.
But the upshot is that this is a very different beast from the single_access_token, and the two can't be mixed.

Resources