Commit 917a38baabac5d63c80e99dbc21fa3d2d95feea0

Migrated enki.yml author info to an Author model to support multiple admin logins

Added Author model
Admin/SessionsController changd to use OpenID-aware Author model
Fix up all references to enki.yml author information
Added notification_exceptions to enki.yml for specifying exception notification email address

Commit diff

app/controllers/admin/base_controller.rb

 
1414 end
1515
1616 def require_login_or_enki_hash
17 unless session[:logged_in] || request.headers['HTTP_X_ENKIHASH'] == hash_request(request)
17 unless logged_in_author || request.headers['HTTP_X_ENKIHASH'] == hash_request(request)
1818 return render(:text => false.to_yaml, :status => :forbidden) if params[:format] == 'yaml'
1919 return redirect_to(admin_session_path)
2020 end
2929 raise("Invalid request: YAML must specify a hash") unless ret.is_a?(Hash)
3030 ret
3131 end
32
33 def logged_in_author
34 @current_author ||= session[:author_id] && Author.find(session[:author_id])
35 end
3236end
toggle raw diff

app/controllers/admin/posts_controller.rb

 
4848 end
4949
5050 def build_object
51 @current_object = Post.new(object_parameters) do |post|
51 @current_object = logged_in_author.posts.build(object_parameters) do |post|
5252 post.published_at = Time.now
5353 end
5454 end
toggle raw diff

app/controllers/admin/sessions_controller.rb

 
1515 def create
1616 begin
1717 return if open_id_authenticate(params[:openid_url]) do |response|
18 if URI.parse(response.identity_url) == URI.parse(config[:author, :open_id])
19 session[:logged_in] = true
18 if author = Author.with_open_id(response.identity_url)
19 session[:author_id] = author.id
2020 redirect_to(admin_posts_path)
2121 return
2222 else
3232 end
3333
3434 def destroy
35 session[:logged_in] = false
35 session[:author_id] = nil
3636 redirect_to('/')
3737 end
3838end
toggle raw diff

app/helpers/application_helper.rb

 
11# Methods added to this helper will be available to all templates in the application.
22module ApplicationHelper
33 def author
4 Struct.new(:name, :email).new(config[:author][:name], config[:author][:email])
4 Author.find(:first)
55 end
66
77 def open_id_delegation_link_tags(server, delegate)
toggle raw diff

app/models/author.rb

 
11class Author < ActiveRecord::Base
2 validates_presence_of :name, :email, :open_id
2 validates_presence_of :name, :email
3 validate :open_id_valid
4
5 has_many :posts
6
7 class << self
8 # Finds the author with open_id matching the given open_id address
9 def with_open_id(open_id)
10 uri = URI.parse(open_id)
11 find(:all).detect {|a| a.open_id == uri}
12 end
13 end
14
15 def open_id
16 @open_id || URI.parse(read_attribute(:open_id))
17 end
18 def open_id=(uri)
19 @open_id = begin
20 URI.parse(uri)
21 rescue URI::InvalidURIError
22 nil
23 end
24 write_attribute(:open_id, uri.to_s)
25 end
26
27 private
28 def open_id_valid
29 unless self.open_id && (self.open_id.is_a?(URI::HTTP) || self.open_id.is_a?(URI::HTTPS))
30 errors.add(:open_id, "not a valid OpenID URL")
31 end
32 end
333end
toggle raw diff

app/models/post.rb

 
33
44 acts_as_taggable
55
6 belongs_to :author
67 has_many :comments, :dependent => :destroy
78 has_many :approved_comments, :class_name => 'Comment'
89
toggle raw diff

app/views/admin/posts/_post_form.html.erb

 
8484<%= check_box 'post', 'is_active', :checked => (@post.is_active ? 'checked' : ''), :onchange => "setPingCheck(this, this.form.elements['ping'], '#{(get_pref('PING_BY_DEFAULT') == 'yes' ? '1' : '0')}');" %><label for="post_is_active" class="left">&nbsp;<span class="small gray">If the box is checked, the post appears on your site</span></label></td></tr>
8585
8686<% if @authors.length < 2 %>
87<%= hidden_field 'post', 'author_id', :value => @authors[0].id.to_s %>
87<%= hidden_field 'post', 'author_id', :value => @post.author.id.to_s %>
8888<% else %>
8989<tr id="dt2" style="display: none;"><td><label for="post_author_id"><b>Author</b></label></td><td class="fields">
90<%= select_tag 'post[author_id]', create_author_select_options(@authors, (@post.author_id == 0 ? get_author_id(request.cookies[SL_CONFIG[:USER_EMAIL_COOKIE]]) : @post.author_id), false) %></td></tr>
90<%= select_tag 'post[author_id]', create_author_select_options(@authors, ((@post.author && @post.author.id) ? get_author_id(request.cookies[SL_CONFIG[:USER_EMAIL_COOKIE]]) : @post.author.id), false) %></td></tr>
9191<% end %>
9292
9393<tr id="dt3" style="display: none;"><td><label for="post_created_at"><b>Created at</b></label></td><td>
toggle raw diff

app/views/layouts/main.html.erb

 
4545 </form>
4646 </div>
4747
48 <div id="footer"><%= config[:title] %> &#169; <%= config[:author, :name] %>. Valid <a href="http://validator.w3.org/check?uri=referer">XHTML</a> and <%= link_to "ATOM", "http://feedvalidator.org/check.cgi?url=#{config[:url]}/posts.atom" %>. Powered by <a href="http://www.enkiblog.com" title="A ruby on rails blogging app for the fashionable developer">Enki</a>.</div>
48 <div id="footer"><%= config[:title] %> &#169; <%= author.name %>. Valid <a href="http://validator.w3.org/check?uri=referer">XHTML</a> and <%= link_to "ATOM", "http://feedvalidator.org/check.cgi?url=#{config[:url]}/posts.atom" %>. Powered by <a href="http://www.enkiblog.com" title="A ruby on rails blogging app for the fashionable developer">Enki</a>.</div>
4949 </div>
5050</body>
5151</html>
toggle raw diff

config/enki.yml

 
11title: My Enki Blog
22url: http://enkiblog.com
3author:
4 name: Don Alias
5 email: don@enkiblog.com
6 open_id: http://enkiblog.com
3exception_notifications: don@enkiblog.com
74
85# Delete the following section if your site will not be acting as an OpenID delegate
96open_id_delegation:
toggle raw diff

config/initializers/exception_notifier.rb

 
1ExceptionNotifier.exception_recipients = [Enki::Config.new("config/enki.yml")[:author, :email]]
1ExceptionNotifier.exception_recipients = [Enki::Config.new("config/enki.yml")[:exception_notifications]]
toggle raw diff

db/migrate/008_create_author_based_on_enki_yml.rb

 
1require 'uri'
2require 'yaml'
3
4class CreateAuthorBasedOnEnkiYml < ActiveRecord::Migration
5
6 # Kept here for old time's sake
7 class Author < ActiveRecord::Base
8 validates_presence_of :name, :email
9 validate :open_id_valid
10
11 class << self
12 # Finds the author with open_id matching the given open_id address
13 def with_open_id(open_id)
14 uri = URI.parse(open_id)
15 find(:all).detect {|a| a.open_id == uri}
16 end
17 end
18
19 def open_id
20 @open_id || URI.parse(read_attribute(:open_id))
21 end
22 def open_id=(uri)
23 @open_id = begin
24 URI.parse(uri)
25 rescue URI::InvalidURIError
26 nil
27 end
28 write_attribute(:open_id, uri.to_s)
29 end
30
31 private
32 def open_id_valid
33 unless self.open_id && (self.open_id.is_a?(URI::HTTP) || self.open_id.is_a?(URI::HTTPS))
34 errors.add(:open_id, "not a valid OpenID URL")
35 end
36 end
37 end
38
39 def self.up
40 enki_config = YAML.load(File.read(File.join(RAILS_ROOT, "config", "enki.yml")))
41 if author = enki_config["author"]
42 author = Author.new :name => enki_yml_author["name"],
43 :email => enki_yml_author["email"],
44 :open_id => enki_yml_author["open_id"]
45 author.save!
46 STDERR.puts "You can now remove the author fields from config/enki.yml"
47 STDERR.puts "Dont forget to add an exception_notifications: entry with your email address to config/enki.yml"
48 end
49 rescue ActiveRecord::RecordInvalid
50 raise "Migration from the legacy author fields in your config/enki.yml failed:\n#{author.errors.full_messages.join("\n\t")}"
51 end
52
53 def self.down
54 end
55end
toggle raw diff

db/schema.rb

 
99#
1010# It's strongly recommended to check this file into your version control system.
1111
12ActiveRecord::Schema.define(:version => 7) do
12ActiveRecord::Schema.define(:version => 8) do
1313
1414 create_table "authors", :force => true do |t|
1515 t.string "name"
toggle raw diff

spec/controllers/admin/sessions_controller_spec.rb

 
3535 end
3636
3737 it 'logs out the current session' do
38 session[:logged_in].should == false
38 session[:author_id].should == nil
3939 end
4040
4141 it 'redirects to /' do
toggle raw diff

spec/models/authors_spec.rb

 
2222 end
2323
2424 it 'is invalid with no open_id' do
25 Author.new(valid_author_attributes.merge(:open_id => nil)).should_not be_valid
26 end
27 it 'is invalid with a blank open_id' do
2528 Author.new(valid_author_attributes.merge(:open_id => '')).should_not be_valid
2629 end
30 it 'is invalid without a HTTP or HTTPS open_id' do
31 Author.new(valid_author_attributes.merge(:open_id => 'something.com')).should_not be_valid
32 Author.new(valid_author_attributes.merge(:open_id => 'ftp://something.com')).should_not be_valid
33 end
2734end
35
36describe 'Author', 'open_id' do
37 setup do
38 @author = Author.new
39 end
40 it 'can be set with a URI string representation' do
41 @author.open_id = "http://enkiblog.com"
42 @author.open_id.should eql(URI.parse("http://enkiblog.com"))
43 end
44 it 'can be set with a URI' do
45 @author.open_id = URI.parse("http://enkiblog.com")
46 @author.open_id.should eql(URI.parse("http://enkiblog.com"))
47 end
48end
49
50describe 'Author', 'with_open_id' do
51 setup do
52 @author = Author.create! :name => "Don Alias",
53 :email => "don@enkiblog.com",
54 :open_id => "http://enkiblog.com"
55
56 end
57 it "should return the author with matching open_id" do
58 Author.with_open_id("http://enkiblog.com").should eql(@author)
59 end
60 it "should return nil if there's no author with matching open_id" do
61 Author.with_open_id("http://somesite.com").should be_nil
62 end
63end
toggle raw diff