mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-17 15:50:13 +01:00
This changeset introduces some integrated web service type features that take advantage
of the Rails 1.1 responds_to functionality. It also lays a foundation for future API enhancements. Basically, if you request the /projects, contexts/ or notes/ URLs with a client that specifies that it wants XML, Tracks will return XML. See DHH on the Accept header (http://www.loudthinking.com/arc/000572.html). But there's a wrinkle. The controller actions mapped to these URLs are protected by an authentication filter. In normal use, Tracks redirects an unauthenticated user to the login screen for session-based authentication. I've added a secondary authentication check that looks for a valid username and password coming from HTTP_BASIC authentication. To test out the new functionality, try this: curl -H 'Accept: application/xml' --basic --user YOUR_TRACKS_USERNAME:YOUR_TRACKS_PASSWORD http://localhost:3000/projects/ curl -H 'Accept: application/xml' --basic --user YOUR_TRACKS_USERNAME:YOUR_TRACKS_PASSWORD http://localhost:3000/contexts/ curl -H 'Accept: application/xml' --basic --user YOUR_TRACKS_USERNAME:YOUR_TRACKS_PASSWORD http://localhost:3000/notes/ HTTP_BASIC sends passwords in plain text, so the use of https is encouraged. I haven't tested this on a shared host yet, but Coda Hale, whose simple_http_auth inspired this solution and provided some copy and paste code for it (thanks, Coda!), has some notes about how to make it work in his plugin readme (http://svn.codahale.com/simple_http_auth/README). To wit, putting the following in .htaccess: RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] My thinking on this architecture is as follows: 1) Follow the spirit of responds_to and DRY to leverage existing controller code for API functionality 2) Get away from using the user token for API interactions. Let's keep it for feeds, so it's basically a "lite" form of security for read-only feeds. 3) Keep Tracks in shape to adopt the simply_restful plugin being developed alongside Rails Edge There's no real new functionality in this release that the existing API didn't provide (except for seeing your notes as XML, and somehow I don't think people are clamoring for that), but this work is an important step to being able to implement the types of API features people have been asking for. While I was at it, I did some refactoring to the login_controller for readability and style. Finally, I replaced the activity indicator graphic to work with the new navigation background color. git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@251 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
parent
3da6fe2525
commit
463a61f514
7 changed files with 93 additions and 43 deletions
|
|
@ -2,7 +2,7 @@ class ContextController < ApplicationController
|
|||
|
||||
helper :todo
|
||||
|
||||
before_filter :login_required
|
||||
prepend_before_filter :login_required
|
||||
layout "standard"
|
||||
|
||||
def index
|
||||
|
|
@ -16,6 +16,10 @@ class ContextController < ApplicationController
|
|||
def list
|
||||
self.init
|
||||
@page_title = "TRACKS::List Contexts"
|
||||
respond_to do |wants|
|
||||
wants.html
|
||||
wants.xml { render :xml => @contexts.to_xml }
|
||||
end
|
||||
end
|
||||
|
||||
# Filter the projects to show just the one passed in the URL
|
||||
|
|
|
|||
|
|
@ -11,12 +11,8 @@ class LoginController < ApplicationController
|
|||
session['user_id'] = @user.id
|
||||
# If checkbox on login page checked, we don't expire the session after 1 hour
|
||||
# of inactivity
|
||||
session['noexpiry']= params['user_noexpiry']
|
||||
if session['noexpiry'] == "on"
|
||||
msg = "will not expire."
|
||||
else
|
||||
msg = "will expire after 1 hour of inactivity."
|
||||
end
|
||||
session['noexpiry'] = params['user_noexpiry']
|
||||
msg = (should_expire_sessions?) ? "will not expire." : "will expire after 1 hour of inactivity."
|
||||
flash['notice'] = "Login successful: session #{msg}"
|
||||
redirect_back_or_default :controller => "todo", :action => "list"
|
||||
else
|
||||
|
|
@ -27,31 +23,19 @@ class LoginController < ApplicationController
|
|||
end
|
||||
|
||||
def signup
|
||||
if User.find_all.empty? # signup the first user as admin
|
||||
if User.find_all.empty? # the first user of the system
|
||||
@page_title = "Sign up as the admin user"
|
||||
elsif session['user_id'] # we have someone logged in
|
||||
get_admin_user
|
||||
if session['user_id'] == @admin.id # logged in user is admin, so allow signup
|
||||
@user = get_new_user
|
||||
else
|
||||
admin = User.find_admin
|
||||
if current_user_is admin
|
||||
@page_title = "Sign up a new user"
|
||||
else
|
||||
@user = get_new_user
|
||||
else # all other situations (i.e. a non-admin is logged in, or no one is logged in, but we have some users)
|
||||
@page_title = "No signups"
|
||||
@admin_email = @admin.preferences["admin_email"]
|
||||
@admin_email = admin.preferences["admin_email"]
|
||||
render :action => "nosignup"
|
||||
return
|
||||
end
|
||||
else # no-one logged in, but we have some Users
|
||||
get_admin_user
|
||||
@page_title = "No signups"
|
||||
@admin_email = @admin.preferences["admin_email"]
|
||||
render :action => "nosignup"
|
||||
return
|
||||
end
|
||||
|
||||
if session['new_user']
|
||||
@user = session['new_user']
|
||||
session['new_user'] = nil
|
||||
else
|
||||
@user = User.new
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -74,7 +58,7 @@ class LoginController < ApplicationController
|
|||
end
|
||||
|
||||
def delete
|
||||
if params['id'] and ( params['id'] = @user.id or @user.is_admin )
|
||||
if params['id'] and ( params['id'] == @user.id or @user.is_admin )
|
||||
@user = User.find(params['id'])
|
||||
# TODO: Maybe it would be better to mark deleted. That way user deletes can be reversed.
|
||||
@user.destroy
|
||||
|
|
@ -93,12 +77,8 @@ class LoginController < ApplicationController
|
|||
# Gets called by periodically_call_remote to check whether
|
||||
# the session has timed out yet
|
||||
unless session == nil
|
||||
return if @controller_name == 'feed' or session['noexpiry'] == "on"
|
||||
# If the method is called by the feed controller
|
||||
# (which we don't have under session control)
|
||||
# or if we checked the box to keep logged in on login
|
||||
# then the session is not going to get called
|
||||
if session
|
||||
return unless should_expire_sessions?
|
||||
# Get expiry time (allow ten seconds window for the case where we have none)
|
||||
expiry_time = session['expiry_time'] || Time.now + 10
|
||||
@time_left = expiry_time - Time.now
|
||||
|
|
@ -111,4 +91,24 @@ class LoginController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_new_user
|
||||
if session['new_user']
|
||||
user = session['new_user']
|
||||
session['new_user'] = nil
|
||||
else
|
||||
user = User.new
|
||||
end
|
||||
user
|
||||
end
|
||||
|
||||
def current_user_is(user)
|
||||
session['user_id'] && session['user_id'] == user.id
|
||||
end
|
||||
|
||||
def should_expire_sessions?
|
||||
session['noexpiry'] != "on"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
class NoteController < ApplicationController
|
||||
|
||||
model :user
|
||||
before_filter :login_required
|
||||
prepend_before_filter :login_required
|
||||
|
||||
layout "standard"
|
||||
|
||||
def index
|
||||
@all_notes = @user.notes
|
||||
@page_title = "TRACKS::All notes"
|
||||
respond_to do |wants|
|
||||
wants.html
|
||||
wants.xml { render :xml => @all_notes.to_xml }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ class ProjectController < ApplicationController
|
|||
model :todo
|
||||
|
||||
helper :todo
|
||||
before_filter :login_required
|
||||
prepend_before_filter :login_required
|
||||
|
||||
layout "standard"
|
||||
|
||||
|
|
@ -18,6 +18,10 @@ class ProjectController < ApplicationController
|
|||
def list
|
||||
init
|
||||
@page_title = "TRACKS::List Projects"
|
||||
respond_to do |wants|
|
||||
wants.html
|
||||
wants.xml { render :xml => @projects.to_xml }
|
||||
end
|
||||
end
|
||||
|
||||
# Filter the projects to show just the one passed in the URL
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ class User < ActiveRecord::Base
|
|||
find_first(["login = ? AND password = ?", login, sha1(pass)])
|
||||
end
|
||||
|
||||
def self.find_admin
|
||||
find_first([ "is_admin = ?", true ])
|
||||
end
|
||||
|
||||
def change_password(pass,pass_confirm)
|
||||
self.password = pass
|
||||
self.password_confirmation = pass_confirm
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ module LoginSystem
|
|||
return true
|
||||
end
|
||||
|
||||
http_user, http_pass = get_basic_auth_data
|
||||
if user = User.authenticate(http_user, http_pass)
|
||||
session['user_id'] = user.id
|
||||
return true
|
||||
end
|
||||
|
||||
# store current location so that we can
|
||||
# come back after the user logged in
|
||||
store_location
|
||||
|
|
@ -65,10 +71,10 @@ module LoginSystem
|
|||
# example use :
|
||||
# a popup window might just close itself for instance
|
||||
def access_denied
|
||||
if request.xhr?
|
||||
render :partial => 'login/redirect_to_login'
|
||||
else
|
||||
redirect_to :controller=>"login", :action =>"login"
|
||||
respond_to do |wants|
|
||||
wants.html { redirect_to :controller=>"login", :action =>"login" }
|
||||
wants.js { render :partial => 'login/redirect_to_login' }
|
||||
wants.xml { basic_auth_denied }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -88,4 +94,32 @@ module LoginSystem
|
|||
end
|
||||
end
|
||||
|
||||
# HTTP Basic auth code adapted from Coda Hale's simple_http_auth plugin. Thanks, Coda!
|
||||
def get_basic_auth_data
|
||||
|
||||
auth_locations = ['REDIRECT_REDIRECT_X_HTTP_AUTHORIZATION',
|
||||
'REDIRECT_X_HTTP_AUTHORIZATION',
|
||||
'X-HTTP_AUTHORIZATION', 'HTTP_AUTHORIZATION']
|
||||
|
||||
authdata = nil
|
||||
for location in auth_locations
|
||||
if request.env.has_key?(location)
|
||||
authdata = request.env[location].to_s.split
|
||||
end
|
||||
end
|
||||
if authdata and authdata[0] == 'Basic'
|
||||
user, pass = Base64.decode64(authdata[1]).split(':')[0..1]
|
||||
else
|
||||
user, pass = ['', '']
|
||||
end
|
||||
return user, pass
|
||||
end
|
||||
|
||||
def basic_auth_denied
|
||||
response.headers["Status"] = "Unauthorized"
|
||||
response.headers["WWW-Authenticate"] = "Basic realm=\"'Tracks Login Required'\""
|
||||
render :text => "401 Unauthorized: You are not authorized to interact with Tracks.", :status => 401
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.5 KiB |
Loading…
Add table
Add a link
Reference in a new issue