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
| |   |
| 14 | 14 | end |
| 15 | 15 | |
| 16 | 16 | 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) |
| 18 | 18 | return render(:text => false.to_yaml, :status => :forbidden) if params[:format] == 'yaml' |
| 19 | 19 | return redirect_to(admin_session_path) |
| 20 | 20 | end |
| … | … | |
| 29 | 29 | raise("Invalid request: YAML must specify a hash") unless ret.is_a?(Hash) |
| 30 | 30 | ret |
| 31 | 31 | end |
| 32 | |
| 33 | def logged_in_author |
| 34 | @current_author ||= session[:author_id] && Author.find(session[:author_id]) |
| 35 | end |
| 32 | 36 | end |
| toggle raw diff |
--- a/app/controllers/admin/base_controller.rb
+++ b/app/controllers/admin/base_controller.rb
@@ -14,7 +14,7 @@ class Admin::BaseController < ApplicationController
end
def require_login_or_enki_hash
- unless session[:logged_in] || request.headers['HTTP_X_ENKIHASH'] == hash_request(request)
+ unless logged_in_author || request.headers['HTTP_X_ENKIHASH'] == hash_request(request)
return render(:text => false.to_yaml, :status => :forbidden) if params[:format] == 'yaml'
return redirect_to(admin_session_path)
end
@@ -29,4 +29,8 @@ class Admin::BaseController < ApplicationController
raise("Invalid request: YAML must specify a hash") unless ret.is_a?(Hash)
ret
end
+
+ def logged_in_author
+ @current_author ||= session[:author_id] && Author.find(session[:author_id])
+ end
end |
| |   |
| 15 | 15 | def create |
| 16 | 16 | begin |
| 17 | 17 | 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 |
| 20 | 20 | redirect_to(admin_posts_path) |
| 21 | 21 | return |
| 22 | 22 | else |
| … | … | |
| 32 | 32 | end |
| 33 | 33 | |
| 34 | 34 | def destroy |
| 35 | | session[:logged_in] = false |
| 35 | session[:author_id] = nil |
| 36 | 36 | redirect_to('/') |
| 37 | 37 | end |
| 38 | 38 | end |
| toggle raw diff |
--- a/app/controllers/admin/sessions_controller.rb
+++ b/app/controllers/admin/sessions_controller.rb
@@ -15,8 +15,8 @@ class Admin::SessionsController < ApplicationController
def create
begin
return if open_id_authenticate(params[:openid_url]) do |response|
- if URI.parse(response.identity_url) == URI.parse(config[:author, :open_id])
- session[:logged_in] = true
+ if author = Author.with_open_id(response.identity_url)
+ session[:author_id] = author.id
redirect_to(admin_posts_path)
return
else
@@ -32,7 +32,7 @@ class Admin::SessionsController < ApplicationController
end
def destroy
- session[:logged_in] = false
+ session[:author_id] = nil
redirect_to('/')
end
end |
| |   |
| 1 | 1 | # Methods added to this helper will be available to all templates in the application. |
| 2 | 2 | module ApplicationHelper |
| 3 | 3 | def author |
| 4 | | Struct.new(:name, :email).new(config[:author][:name], config[:author][:email]) |
| 4 | Author.find(:first) |
| 5 | 5 | end |
| 6 | 6 | |
| 7 | 7 | def open_id_delegation_link_tags(server, delegate) |
| toggle raw diff |
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,7 +1,7 @@
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
def author
- Struct.new(:name, :email).new(config[:author][:name], config[:author][:email])
+ Author.find(:first)
end
def open_id_delegation_link_tags(server, delegate) |
| |   |
| 1 | 1 | class 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 |
| 3 | 33 | end |
| toggle raw diff |
--- a/app/models/author.rb
+++ b/app/models/author.rb
@@ -1,3 +1,33 @@
class Author < ActiveRecord::Base
- validates_presence_of :name, :email, :open_id
+ validates_presence_of :name, :email
+ validate :open_id_valid
+
+ has_many :posts
+
+ class << self
+ # Finds the author with open_id matching the given open_id address
+ def with_open_id(open_id)
+ uri = URI.parse(open_id)
+ find(:all).detect {|a| a.open_id == uri}
+ end
+ end
+
+ def open_id
+ @open_id || URI.parse(read_attribute(:open_id))
+ end
+ def open_id=(uri)
+ @open_id = begin
+ URI.parse(uri)
+ rescue URI::InvalidURIError
+ nil
+ end
+ write_attribute(:open_id, uri.to_s)
+ end
+
+ private
+ def open_id_valid
+ unless self.open_id && (self.open_id.is_a?(URI::HTTP) || self.open_id.is_a?(URI::HTTPS))
+ errors.add(:open_id, "not a valid OpenID URL")
+ end
+ end
end |
| |   |
| 84 | 84 | <%= 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"> <span class="small gray">If the box is checked, the post appears on your site</span></label></td></tr> |
| 85 | 85 | |
| 86 | 86 | <% 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 %> |
| 88 | 88 | <% else %> |
| 89 | 89 | <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> |
| 91 | 91 | <% end %> |
| 92 | 92 | |
| 93 | 93 | <tr id="dt3" style="display: none;"><td><label for="post_created_at"><b>Created at</b></label></td><td> |
| toggle raw diff |
--- a/app/views/admin/posts/_post_form.html.erb
+++ b/app/views/admin/posts/_post_form.html.erb
@@ -84,10 +84,10 @@ end
<%= 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"> <span class="small gray">If the box is checked, the post appears on your site</span></label></td></tr>
<% if @authors.length < 2 %>
-<%= hidden_field 'post', 'author_id', :value => @authors[0].id.to_s %>
+<%= hidden_field 'post', 'author_id', :value => @post.author.id.to_s %>
<% else %>
<tr id="dt2" style="display: none;"><td><label for="post_author_id"><b>Author</b></label></td><td class="fields">
-<%= 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>
+<%= 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>
<% end %>
<tr id="dt3" style="display: none;"><td><label for="post_created_at"><b>Created at</b></label></td><td> |
| |   |
| 45 | 45 | </form> |
| 46 | 46 | </div> |
| 47 | 47 | |
| 48 | | <div id="footer"><%= config[:title] %> © <%= 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] %> © <%= 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> |
| 49 | 49 | </div> |
| 50 | 50 | </body> |
| 51 | 51 | </html> |
| toggle raw diff |
--- a/app/views/layouts/main.html.erb
+++ b/app/views/layouts/main.html.erb
@@ -45,7 +45,7 @@
</form>
</div>
- <div id="footer"><%= config[:title] %> © <%= 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>
+ <div id="footer"><%= config[:title] %> © <%= 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>
</div>
</body>
</html> |
| |   |
| 1 | require 'uri' |
| 2 | require 'yaml' |
| 3 | |
| 4 | class 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 |
| 55 | end |
| toggle raw diff |
--- /dev/null
+++ b/db/migrate/008_create_author_based_on_enki_yml.rb
@@ -0,0 +1,55 @@
+require 'uri'
+require 'yaml'
+
+class CreateAuthorBasedOnEnkiYml < ActiveRecord::Migration
+
+ # Kept here for old time's sake
+ class Author < ActiveRecord::Base
+ validates_presence_of :name, :email
+ validate :open_id_valid
+
+ class << self
+ # Finds the author with open_id matching the given open_id address
+ def with_open_id(open_id)
+ uri = URI.parse(open_id)
+ find(:all).detect {|a| a.open_id == uri}
+ end
+ end
+
+ def open_id
+ @open_id || URI.parse(read_attribute(:open_id))
+ end
+ def open_id=(uri)
+ @open_id = begin
+ URI.parse(uri)
+ rescue URI::InvalidURIError
+ nil
+ end
+ write_attribute(:open_id, uri.to_s)
+ end
+
+ private
+ def open_id_valid
+ unless self.open_id && (self.open_id.is_a?(URI::HTTP) || self.open_id.is_a?(URI::HTTPS))
+ errors.add(:open_id, "not a valid OpenID URL")
+ end
+ end
+ end
+
+ def self.up
+ enki_config = YAML.load(File.read(File.join(RAILS_ROOT, "config", "enki.yml")))
+ if author = enki_config["author"]
+ author = Author.new :name => enki_yml_author["name"],
+ :email => enki_yml_author["email"],
+ :open_id => enki_yml_author["open_id"]
+ author.save!
+ STDERR.puts "You can now remove the author fields from config/enki.yml"
+ STDERR.puts "Dont forget to add an exception_notifications: entry with your email address to config/enki.yml"
+ end
+ rescue ActiveRecord::RecordInvalid
+ raise "Migration from the legacy author fields in your config/enki.yml failed:\n#{author.errors.full_messages.join("\n\t")}"
+ end
+
+ def self.down
+ end
+end |
| |   |
| 22 | 22 | end |
| 23 | 23 | |
| 24 | 24 | 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 |
| 25 | 28 | Author.new(valid_author_attributes.merge(:open_id => '')).should_not be_valid |
| 26 | 29 | 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 |
| 27 | 34 | end |
| 35 | |
| 36 | describe '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 |
| 48 | end |
| 49 | |
| 50 | describe '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 |
| 63 | end |
| toggle raw diff |
--- a/spec/models/authors_spec.rb
+++ b/spec/models/authors_spec.rb
@@ -22,6 +22,42 @@ describe Author, 'validations' do
end
it 'is invalid with no open_id' do
+ Author.new(valid_author_attributes.merge(:open_id => nil)).should_not be_valid
+ end
+ it 'is invalid with a blank open_id' do
Author.new(valid_author_attributes.merge(:open_id => '')).should_not be_valid
end
+ it 'is invalid without a HTTP or HTTPS open_id' do
+ Author.new(valid_author_attributes.merge(:open_id => 'something.com')).should_not be_valid
+ Author.new(valid_author_attributes.merge(:open_id => 'ftp://something.com')).should_not be_valid
+ end
end
+
+describe 'Author', 'open_id' do
+ setup do
+ @author = Author.new
+ end
+ it 'can be set with a URI string representation' do
+ @author.open_id = "http://enkiblog.com"
+ @author.open_id.should eql(URI.parse("http://enkiblog.com"))
+ end
+ it 'can be set with a URI' do
+ @author.open_id = URI.parse("http://enkiblog.com")
+ @author.open_id.should eql(URI.parse("http://enkiblog.com"))
+ end
+end
+
+describe 'Author', 'with_open_id' do
+ setup do
+ @author = Author.create! :name => "Don Alias",
+ :email => "don@enkiblog.com",
+ :open_id => "http://enkiblog.com"
+
+ end
+ it "should return the author with matching open_id" do
+ Author.with_open_id("http://enkiblog.com").should eql(@author)
+ end
+ it "should return nil if there's no author with matching open_id" do
+ Author.with_open_id("http://somesite.com").should be_nil
+ end
+end
\ No newline at end of file |