diff --git a/app/controllers/authorizations_controller.rb b/app/controllers/authorizations_controller.rb index b2a0540a56ce1d48041caf9a686fe7b93a9d8283..84cf1718e6457fd2c4c355667c811f1d0a86aeb6 100644 --- a/app/controllers/authorizations_controller.rb +++ b/app/controllers/authorizations_controller.rb @@ -21,14 +21,21 @@ class AuthorizationsController < ApplicationController def token if(params[:type] == 'client_associate' && params[:manifest_url]) - client = OAuth2::Provider.client_class.create_from_manifest!(params[:manifest_url]) + manifest = JSON.parse(RestClient.get(params[:manifest_url]).body) - render :json => {:client_id => client.oauth_identifier, - :client_secret => client.oauth_secret, - :expires_in => 0, - :flows_supported => "", - } + message = verify(params[:signed_string], params[:signature], manifest['public_key']) + unless message =='ok' + render :text => message, :status => 403 + else + client = OAuth2::Provider.client_class.create_from_manifest!(manifest) + render :json => {:client_id => client.oauth_identifier, + :client_secret => client.oauth_secret, + :expires_in => 0, + :flows_supported => "", + } + + end else render :text => "bad request", :status => 403 end @@ -45,11 +52,24 @@ class AuthorizationsController < ApplicationController auth.revoke redirect_to authorizations_path end + + + def verify_signature(challenge, signature, serialized_pub_key) + public_key = OpenSSL::PKey::RSA.new(serialized_pub_key) + public_key.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), challenge) + end + + def valid_time?(time) + time.to_i > (Time.now - 5.minutes).to_i + end + + def valid_nonce?(nonce) + OAuth2::Provider.client_class.where(:nonce => nonce).first.nil? + end end OAuth2::Provider.client_class.instance_eval do - def self.create_from_manifest! manifest_url - manifest = JSON.parse(RestClient.get(manifest_url).body) + def self.create_from_manifest! manifest create!(manifest) end end diff --git a/app/views/authorizations/index.html.haml b/app/views/authorizations/index.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..f4b300fca70af851a21daea5a147c0a03c2480b0 --- /dev/null +++ b/app/views/authorizations/index.html.haml @@ -0,0 +1,31 @@ +-# Copyright (c) 2010, Diaspora Inc. This file is +-# licensed under the Affero General Public License version 3 or later. See +-# the COPYRIGHT file. + +#section_header + %h2 + = t('settings') + = render 'shared/settings_nav' + +.span-19.prepend-5.last + %h2 + = t('_applications') + + +#applications_stream.stream + - for app in @applications + + .stream_element{:id => app.id} + .right + = link_to t('delete'), authorization_path(:id => app.id), :method => :delete, :confirm => 'are you sure?' + + - if app.icon_url + = image_tag app.icon_url + + .content + %span.from + = link_to app.name, app.homepage_url + + .info + = app.description + diff --git a/db/migrate/20110614005205_add_nonce_and_public_key_to_oauth_clients.rb b/db/migrate/20110614005205_add_nonce_and_public_key_to_oauth_clients.rb new file mode 100644 index 0000000000000000000000000000000000000000..316651adbc16fab4d97630e79093bdecec1230c5 --- /dev/null +++ b/db/migrate/20110614005205_add_nonce_and_public_key_to_oauth_clients.rb @@ -0,0 +1,13 @@ +class AddNonceAndPublicKeyToOauthClients < ActiveRecord::Migration + def self.up + add_column :oauth_clients, :nonce, :string + add_column :oauth_clients, :public_key, :text + add_index :oauth_clients, :nonce + end + + def self.down + remove_column :oauth_clients, :nonce + remove_column :oauth_clients, :public_key + remove_index :oauth_clients, :nonce + end +end diff --git a/db/schema.rb b/db/schema.rb index 2799dbc43c51acdf2793ee2f0a5b4b431620dced..a28f87267ce8d01eff7062553792a15f2c5982e8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110603212633) do +ActiveRecord::Schema.define(:version => 20110614005205) do create_table "aspect_memberships", :force => true do |t| t.integer "aspect_id", :null => false @@ -228,9 +228,12 @@ ActiveRecord::Schema.define(:version => 20110603212633) do t.text "description" t.string "homepage_url" t.string "icon_url" + t.string "nonce" + t.text "public_key" end add_index "oauth_clients", ["name"], :name => "index_oauth_clients_on_name", :unique => true + add_index "oauth_clients", ["nonce"], :name => "index_oauth_clients_on_nonce" create_table "people", :force => true do |t| t.string "guid", :null => false diff --git a/spec/chubbies/chubbies.private.pem b/spec/chubbies/chubbies.private.pem new file mode 100644 index 0000000000000000000000000000000000000000..13c7a9d51e18a59041e4ed6bc5f52d17e981bde8 --- /dev/null +++ b/spec/chubbies/chubbies.private.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC6PThqopA0Sv1ZrWp+wKz6SPQwWzS2IEGRV1eBE2LEvMk5K2MK +jZqDF6SeolCPQsDww03qIxbtzXiVFtjBaiDw+fLpbnX3VcfoMlfV3IKMPjD8eFUE +2ke3tJzHeXLfXhHq5ubX1PrXcsNwHeNrtvviv9G2BhYMjwi49T0YS27rhwIDAQAB +AoGAUxEhS7asWRalf80wdc6cmJnXiIX1sdIbTKdHWUP7RsLpgX1PlJJqO1RdUYAW +LH/arMh/xRHXlUOTJ/Rjw4kBnZZpPmKpAmkefqi7ZRTvDNj/aVW2DMi6TGU0lpMV +TyXKZtO69FK5uFle/I7nftGuUiVAkPIXMrnXM1oxPjCkUWECQQDuNR6JcxCF+Ioj +bQlemHfwRLyfbAumeXkYyBxdfYu7Sonx+xjptacwWyHTnLaFeoEIAjvrjT0fDNym +QZA0YAnxAkEAyCZjmMwacCGxYxIWiVDfMBCt1fSOQzOAYCVz12ltW1GkUh1kt1p+ +4ui/q38RGu9s4RRxb7h0WNHtKfU/t2aU9wJAXiKZ1cGmFvt4Q7W0TTC9vTK8w9ej +5v78LyHtq7iQfsxfJUSQvNMvpfltrb2xl6Ao26xeV6DyNZIUpJMmVLxbUQJBAJfi +cfAw6tsu5lL9FfcMVOI/tftIA3FBsujDYx4T1jXXRbA+uABf/ywoXdNBsPRUvrYr +ck3KlxgT7jrJ7pk5kk8CQCqtJ1U6RlTTKY0jnwOASSCDctGhTXQWj44/xyGjJF7I +4eHuL18xQTjB3Ag+VRK/z8W5mqfgzonotrhc0pSXxvM= +-----END RSA PRIVATE KEY----- diff --git a/spec/chubbies/chubbies.public.pem b/spec/chubbies/chubbies.public.pem new file mode 100644 index 0000000000000000000000000000000000000000..d7905d77259efcc1935afcb6bb2af851c08f6b3c --- /dev/null +++ b/spec/chubbies/chubbies.public.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6PThqopA0Sv1ZrWp+wKz6SPQw +WzS2IEGRV1eBE2LEvMk5K2MKjZqDF6SeolCPQsDww03qIxbtzXiVFtjBaiDw+fLp +bnX3VcfoMlfV3IKMPjD8eFUE2ke3tJzHeXLfXhHq5ubX1PrXcsNwHeNrtvviv9G2 +BhYMjwi49T0YS27rhwIDAQAB +-----END PUBLIC KEY----- diff --git a/spec/controllers/authorizations_controller_spec.rb b/spec/controllers/authorizations_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..8bc702b54ef3a6ac1cd27a6ab2f4e41fce039f55 --- /dev/null +++ b/spec/controllers/authorizations_controller_spec.rb @@ -0,0 +1,133 @@ +# Copyright (c) 2010, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + +require 'spec_helper' + +describe AuthorizationsController do + before do + sign_in :user, alice + @controller.stub(:current_user).and_return(alice) + + end + + describe '#token' do + before do + manifest = { + "name" => "Chubbies", + "description" => "The best way to chub.", + "homepage_url" => "http://chubbi.es/", + "icon_url" => "#", + 'public_key' => 'public_key!' + }.to_json + + stub_request(:get, "http://chubbi.es/manifest.json"). + to_return(:status => 200, :body => manifest, :headers => {}) + + @params_hash = {:type => 'client_associate', :manifest_url => "http://chubbi.es/manifest.json" } + end + + it 'fetches the manifest' do + post :token, @params_hash + end + + it 'creates a client application' do + lambda { + post :token, @params_hash + }.should change(OAuth2::Provider.client_class, :count).by(1) + end + + it 'verifies the signable string validity(time,nonce,sig)' do + post :token, @params_hash.merge!({:signed_string => 'signable_string', :signature => 'sig'}) + @controller.should_receive(:verify).with('signable_string', 'sig', 'public_key!') + end + end + + describe "#index" do + it 'succeeds' do + get :index + response.should be_success + end + + it 'assigns the auth. & apps for the current user' do + app1 = OAuth2::Provider.client_class.create(:name => "Authorized App") + app2 = OAuth2::Provider.client_class.create(:name => "Unauthorized App") + auth1 = OAuth2::Provider.authorization_class.create(:client => app1, :resource_owner => alice) + auth2 = OAuth2::Provider.authorization_class.create(:client => app1, :resource_owner => bob) + auth3 = OAuth2::Provider.authorization_class.create(:client => app2, :resource_owner => bob) + + get :index + assigns[:authorizations].should == [auth1] + assigns[:applications].should == [app1] + end + end + + describe "#destroy" do + before do + @app1 = OAuth2::Provider.client_class.create(:name => "Authorized App") + @auth1 = OAuth2::Provider.authorization_class.create(:client => @app1, :resource_owner => alice) + @auth2 = OAuth2::Provider.authorization_class.create(:client => @app1, :resource_owner => bob) + end + it 'deletes an authorization' do + lambda{ + delete :destroy, :id => @app1.id + }.should change(OAuth2::Provider.authorization_class, :count).by(-1) + end + end + + describe '#verify' do + it 'checks for valid time' + it 'checks the signature' + it 'checks for valid nonce' + end + + describe '#verify_signature' do + before do + @private_key = OpenSSL::PKey::RSA.new(File.read(Rails.root + "spec/chubbies/chubbies.private.pem")) + + @signable_string = ["http://chubbi.es/",'http://pod.pod/',"#{Time.now.to_i}",'asdfsfasf'].join(';') + @sig = @private_key.sign(OpenSSL::Digest::SHA256.new, @signable_string) + end + + it 'returns true if the signature is valid' do + @public_key = File.read(Rails.root + "spec/chubbies/chubbies.public.pem") + @controller.verify_signature(@signable_string, Base64.encode64(@sig), @public_key).should be_true + end + + it 'returns false if the signature is invalid' do + @signable_string = "something else" + + @public_key = File.read(Rails.root + "spec/chubbies/chubbies.public.pem") + @controller.verify_signature(@signable_string, Base64.encode64(@sig), @public_key).should be_false + end + end + + describe "valid_time?" do + before do + @time = Time.now + Time.stub(:now).and_return(@time) + end + + it "returns true if time is within the last 5 minutes" do + @controller.valid_time?(@time - 4.minutes - 59.seconds).should be_true + end + + it "returns false if time is not within the last 5 minutes" do + @controller.valid_time?(@time - 5.minutes - 1.seconds).should be_false + end + end + + describe 'valid_nonce' do + before do + @app1 = OAuth2::Provider.client_class.create(:name => "Authorized App", :nonce => "abc123") + end + + it 'returns true if its a new nonce' do + @controller.valid_nonce?("lalalala").should be_true + end + + it 'returns false if the nonce was already used' do + @controller.valid_nonce?("abc123").should be_false + end + end +end