Change homepage_url to application_base_url
This commit is contained in:
parent
17917528f6
commit
240eb78b08
9 changed files with 51 additions and 52 deletions
|
|
@ -32,18 +32,18 @@ class AuthorizationsController < ApplicationController
|
||||||
render :text => "bad request: #{params.inspect}", :status => 403
|
render :text => "bad request: #{params.inspect}", :status => 403
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
packaged_manifest = JSON.parse(RestClient.get("#{app_url}/manifest.json").body)
|
packaged_manifest = JSON.parse(RestClient.get("#{app_url}manifest.json").body)
|
||||||
public_key = OpenSSL::PKey::RSA.new(packaged_manifest['public_key'])
|
public_key = OpenSSL::PKey::RSA.new(packaged_manifest['public_key'])
|
||||||
manifest = JWT.decode(packaged_manifest['jwt'], public_key)
|
manifest = JWT.decode(packaged_manifest['jwt'], public_key)
|
||||||
|
|
||||||
message = verify(signed_string, Base64.decode64(params[:signature]), public_key, manifest)
|
message = verify(signed_string, Base64.decode64(params[:signature]), public_key, manifest)
|
||||||
if not (message =='ok')
|
if not (message =='ok')
|
||||||
render :text => message, :status => 403
|
render :text => message, :status => 403
|
||||||
elsif manifest["homepage_url"].match(/^http:\/\/(localhost:\d+|chubbi\.es|cubbi\.es)$/).nil?
|
elsif manifest["application_base_url"].match(/^http:\/\/(localhost:\d+|chubbi\.es|cubbi\.es)\/$/).nil?
|
||||||
# This will only be temporary (less than a month) while we iron out the kinks in Diaspora Connect. Essentially,
|
# This will only be temporary (less than a month) while we iron out the kinks in Diaspora Connect. Essentially,
|
||||||
# whatever we release people will try to work off of and it sucks to build things on top of non-stable things.
|
# whatever we release people will try to work off of and it sucks to build things on top of non-stable things.
|
||||||
# We also started writing a gem that we'll release (around the same time) that makes becoming a Diaspora enabled
|
# We also started writing a gem that we'll release (around the same time) that makes becoming a Diaspora enabled
|
||||||
# ruby project a breeze.
|
# ruby project a breeze.
|
||||||
|
|
||||||
render :nothing => true
|
render :nothing => true
|
||||||
else
|
else
|
||||||
|
|
@ -79,7 +79,7 @@ class AuthorizationsController < ApplicationController
|
||||||
nonce = split[3]
|
nonce = split[3]
|
||||||
|
|
||||||
return 'blank public key' if public_key.n.nil?
|
return 'blank public key' if public_key.n.nil?
|
||||||
return 'the app url in the manifest does not match the url passed in the parameters' if manifest["homepage_url"] != app_url
|
return 'the app url in the manifest does not match the url passed in the parameters' if manifest["application_base_url"] != app_url
|
||||||
return 'key too small, use at least 2048 bits' if public_key.n.num_bits < 2048
|
return 'key too small, use at least 2048 bits' if public_key.n.num_bits < 2048
|
||||||
return "invalid time" unless valid_time?(time)
|
return "invalid time" unless valid_time?(time)
|
||||||
return 'invalid nonce' unless valid_nonce?(nonce)
|
return 'invalid nonce' unless valid_nonce?(nonce)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
class OAuth2::Provider::Models::ActiveRecord::Client
|
class OAuth2::Provider::Models::ActiveRecord::Client
|
||||||
def self.create_or_reset_from_manifest!(manifest, pub_key)
|
def self.create_or_reset_from_manifest!(manifest, pub_key)
|
||||||
if obj = find_by_name(manifest['name'])
|
if obj = find_by_name(manifest['name'])
|
||||||
obj.oauth_identifier = OAuth2::Provider::Random.base62(16)
|
obj.oauth_identifier = OAuth2::Provider::Random.base62(16)
|
||||||
|
|
@ -10,7 +10,7 @@ class OAuth2::Provider::Models::ActiveRecord::Client
|
||||||
:name => manifest["name"],
|
:name => manifest["name"],
|
||||||
:permissions_overview => manifest["permissions_overview"],
|
:permissions_overview => manifest["permissions_overview"],
|
||||||
:description => manifest["description"],
|
:description => manifest["description"],
|
||||||
:homepage_url => manifest["homepage_url"],
|
:application_base_url => manifest["application_base_url"],
|
||||||
:icon_url => manifest["icon_url"],
|
:icon_url => manifest["icon_url"],
|
||||||
:public_key => pub_key.export
|
:public_key => pub_key.export
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@
|
||||||
.stream_element{:id => app.id}
|
.stream_element{:id => app.id}
|
||||||
.right
|
.right
|
||||||
= link_to t('.revoke_access'), authorization_path(:id => app.id), :method => :delete, :confirm => 'are you sure?', :class => "button"
|
= link_to t('.revoke_access'), authorization_path(:id => app.id), :method => :delete, :confirm => 'are you sure?', :class => "button"
|
||||||
|
|
||||||
- if app.icon_url
|
- if app.icon_url
|
||||||
= image_tag(app.homepage_url + app.icon_url, :class => "avatar")
|
= image_tag(app.application_base_url + app.icon_url, :class => "avatar")
|
||||||
|
|
||||||
.content
|
.content
|
||||||
%div.from
|
%div.from
|
||||||
= link_to app.name, app.homepage_url
|
= link_to app.name, app.application_base_url
|
||||||
= app.description
|
= app.description
|
||||||
|
|
||||||
- else
|
- else
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#authorize
|
#authorize
|
||||||
#application-description
|
#application-description
|
||||||
= image_tag(@client.homepage_url + @client.icon_url, :id => 'client-application-image')
|
= image_tag(@client.application_base_url + @client.icon_url, :id => 'client-application-image')
|
||||||
%br
|
%br
|
||||||
%strong
|
%strong
|
||||||
= @client.name
|
= @client.name
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
class AddOAuth2Support < ActiveRecord::Migration
|
class AddOAuth2Support < ActiveRecord::Migration
|
||||||
def self.up
|
def self.up
|
||||||
create_table 'oauth_clients', :force => true do |t|
|
create_table 'oauth_clients', :force => true do |t|
|
||||||
t.string 'name', :limit => 127, :null => false
|
t.string 'name', :limit => 127, :null => false
|
||||||
t.text 'description', :null => false
|
t.text 'description', :null => false
|
||||||
t.string 'homepage_url', :limit => 127, :null => false
|
t.string 'application_base_url', :limit => 127, :null => false
|
||||||
t.string 'icon_url', :limit => 127, :null => false
|
t.string 'icon_url', :limit => 127, :null => false
|
||||||
|
|
||||||
t.string 'oauth_identifier', :limit => 32, :null => false
|
t.string 'oauth_identifier', :limit => 32, :null => false
|
||||||
t.string 'oauth_secret', :limit => 32, :null => false
|
t.string 'oauth_secret', :limit => 32, :null => false
|
||||||
t.string 'nonce', :limit => 64
|
t.string 'nonce', :limit => 64
|
||||||
t.text 'public_key', :null => false
|
t.text 'public_key', :null => false
|
||||||
t.text 'permissions_overview', :null => false
|
t.text 'permissions_overview', :null => false
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index :oauth_clients, :name, :unique => true
|
add_index :oauth_clients, :name, :unique => true
|
||||||
add_index :oauth_clients, :homepage_url, :unique => true
|
add_index :oauth_clients, :application_base_url, :unique => true
|
||||||
add_index :oauth_clients, :nonce, :unique => true
|
add_index :oauth_clients, :nonce, :unique => true
|
||||||
|
|
||||||
create_table 'oauth_authorization_codes', :force => true do |t|
|
create_table 'oauth_authorization_codes', :force => true do |t|
|
||||||
|
|
@ -56,7 +56,7 @@ class AddOAuth2Support < ActiveRecord::Migration
|
||||||
drop_table 'oauth_authorization_codes'
|
drop_table 'oauth_authorization_codes'
|
||||||
|
|
||||||
remove_index :oauth_clients, :column => :nonce
|
remove_index :oauth_clients, :column => :nonce
|
||||||
remove_index :oauth_clients, :column => :homepage_url
|
remove_index :oauth_clients, :column => :application_base_url
|
||||||
remove_index :oauth_clients, :column => :name
|
remove_index :oauth_clients, :column => :name
|
||||||
|
|
||||||
drop_table 'oauth_clients'
|
drop_table 'oauth_clients'
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@ ActiveRecord::Schema.define(:version => 20110623210918) do
|
||||||
create_table "oauth_clients", :force => true do |t|
|
create_table "oauth_clients", :force => true do |t|
|
||||||
t.string "name", :limit => 127, :null => false
|
t.string "name", :limit => 127, :null => false
|
||||||
t.text "description", :null => false
|
t.text "description", :null => false
|
||||||
t.string "homepage_url", :limit => 127, :null => false
|
t.string "application_base_url", :limit => 127, :null => false
|
||||||
t.string "icon_url", :limit => 127, :null => false
|
t.string "icon_url", :limit => 127, :null => false
|
||||||
t.string "oauth_identifier", :limit => 32, :null => false
|
t.string "oauth_identifier", :limit => 32, :null => false
|
||||||
t.string "oauth_secret", :limit => 32, :null => false
|
t.string "oauth_secret", :limit => 32, :null => false
|
||||||
|
|
@ -235,7 +235,7 @@ ActiveRecord::Schema.define(:version => 20110623210918) do
|
||||||
t.text "permissions_overview", :null => false
|
t.text "permissions_overview", :null => false
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "oauth_clients", ["homepage_url"], :name => "index_oauth_clients_on_homepage_url", :unique => true
|
add_index "oauth_clients", ["application_base_url"], :name => "index_oauth_clients_on_application_base_url", :unique => true
|
||||||
add_index "oauth_clients", ["name"], :name => "index_oauth_clients_on_name", :unique => true
|
add_index "oauth_clients", ["name"], :name => "index_oauth_clients_on_name", :unique => true
|
||||||
add_index "oauth_clients", ["nonce"], :name => "index_oauth_clients_on_nonce", :unique => true
|
add_index "oauth_clients", ["nonce"], :name => "index_oauth_clients_on_nonce", :unique => true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,10 @@ module Chubbies
|
||||||
d.private_key_path = File.dirname(__FILE__) + "/chubbies.private.pem"
|
d.private_key_path = File.dirname(__FILE__) + "/chubbies.private.pem"
|
||||||
d.public_key_path = File.dirname(__FILE__) + "/chubbies.public.pem"
|
d.public_key_path = File.dirname(__FILE__) + "/chubbies.public.pem"
|
||||||
d.test_mode = true
|
d.test_mode = true
|
||||||
d.application_url = "http://localhost:9292"
|
d.application_base_url = "localhost:9292/"
|
||||||
|
|
||||||
d.manifest_field(:name, "Chubbies")
|
d.manifest_field(:name, "Chubbies")
|
||||||
d.manifest_field(:description, "The best way to chub.")
|
d.manifest_field(:description, "The best way to chub.")
|
||||||
d.manifest_field(:homepage_url, "http://localhost:9292")
|
|
||||||
d.manifest_field(:icon_url, "#")
|
d.manifest_field(:icon_url, "#")
|
||||||
|
|
||||||
d.manifest_field(:permissions_overview, "Chubbi.es wants to post photos to your stream.")
|
d.manifest_field(:permissions_overview, "Chubbi.es wants to post photos to your stream.")
|
||||||
|
|
@ -73,10 +72,10 @@ module Chubbies
|
||||||
get '/account' do
|
get '/account' do
|
||||||
if params['id'] && user = User.where(:id => params['id']).first
|
if params['id'] && user = User.where(:id => params['id']).first
|
||||||
if user.access_token
|
if user.access_token
|
||||||
begin
|
begin
|
||||||
@resource_response = user.access_token.token.get("/api/v0/me")
|
@resource_response = user.access_token.token.get("/api/v0/me")
|
||||||
haml :response
|
haml :response
|
||||||
rescue OAuth2::AccessDenied
|
rescue OAuth2::AccessDenied
|
||||||
"Token invalid"
|
"Token invalid"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -13,19 +13,19 @@ describe AuthorizationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in :user, alice
|
sign_in :user, alice
|
||||||
@controller.stub(:current_user).and_return(alice)
|
@controller.stub(:current_user).and_return(alice)
|
||||||
|
|
||||||
@time = Time.now
|
@time = Time.now
|
||||||
Time.stub(:now).and_return(@time)
|
Time.stub(:now).and_return(@time)
|
||||||
@nonce = 'asdfsfasf'
|
@nonce = 'asdfsfasf'
|
||||||
@signed_string = ["http://chubbi.es",'http://pod.pod',"#{Time.now.to_i}", @nonce].join(';')
|
@signed_string = ["http://chubbi.es/",'http://pod.pod',"#{Time.now.to_i}", @nonce].join(';')
|
||||||
@signature = @private_key.sign(OpenSSL::Digest::SHA256.new, @signed_string)
|
@signature = @private_key.sign(OpenSSL::Digest::SHA256.new, @signed_string)
|
||||||
|
|
||||||
@manifest = {
|
@manifest = {
|
||||||
"name" => "Chubbies",
|
"name" => "Chubbies",
|
||||||
"description" => "The best way to chub.",
|
"description" => "The best way to chub.",
|
||||||
"homepage_url" => "http://chubbi.es",
|
"application_base_url" => "http://chubbi.es/",
|
||||||
"icon_url" => "#",
|
"icon_url" => "#",
|
||||||
"permissions_overview" => "I will use the permissions this way!",
|
"permissions_overview" => "I will use the permissions this way!",
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +35,7 @@ describe AuthorizationsController do
|
||||||
before do
|
before do
|
||||||
packaged_manifest = {:public_key => @public_key.export, :jwt => JWT.encode(@manifest, @private_key, "RS256")}.to_json
|
packaged_manifest = {:public_key => @public_key.export, :jwt => JWT.encode(@manifest, @private_key, "RS256")}.to_json
|
||||||
|
|
||||||
stub_request(:get, "http://chubbi.es/manifest.json").
|
stub_request(:get, "http://chubbi.es/manifest.json").
|
||||||
to_return(:status => 200, :body => packaged_manifest, :headers => {})
|
to_return(:status => 200, :body => packaged_manifest, :headers => {})
|
||||||
|
|
||||||
@params_hash = {:type => 'client_associate', :signed_string => Base64.encode64(@signed_string), :signature => Base64.encode64(@signature)}
|
@params_hash = {:type => 'client_associate', :signed_string => Base64.encode64(@signed_string), :signature => Base64.encode64(@signature)}
|
||||||
|
|
@ -46,14 +46,14 @@ describe AuthorizationsController do
|
||||||
manifest = {
|
manifest = {
|
||||||
"name" => "Chubbies",
|
"name" => "Chubbies",
|
||||||
"description" => "The best way to chub.",
|
"description" => "The best way to chub.",
|
||||||
"homepage_url" => url,
|
"application_base_url" => url,
|
||||||
"icon_url" => "#",
|
"icon_url" => "#",
|
||||||
"permissions_overview" => "I will use the permissions this way!",
|
"permissions_overview" => "I will use the permissions this way!",
|
||||||
}
|
}
|
||||||
|
|
||||||
packaged_manifest = {:public_key => @public_key.export, :jwt => JWT.encode(manifest, @private_key, "RS256")}.to_json
|
packaged_manifest = {:public_key => @public_key.export, :jwt => JWT.encode(manifest, @private_key, "RS256")}.to_json
|
||||||
|
|
||||||
stub_request(:get, "#{url}/manifest.json").
|
stub_request(:get, "#{url}manifest.json").
|
||||||
to_return(:status => 200, :body => packaged_manifest, :headers => {})
|
to_return(:status => 200, :body => packaged_manifest, :headers => {})
|
||||||
|
|
||||||
@signed_string = [url,'http://pod.pod',"#{Time.now.to_i}", @nonce].join(';')
|
@signed_string = [url,'http://pod.pod',"#{Time.now.to_i}", @nonce].join(';')
|
||||||
|
|
@ -62,21 +62,21 @@ describe AuthorizationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders something for chubbies ' do
|
it 'renders something for chubbies ' do
|
||||||
prepare_manifest("http://chubbi.es")
|
prepare_manifest("http://chubbi.es/")
|
||||||
@controller.stub!(:verify).and_return('ok')
|
@controller.stub!(:verify).and_return('ok')
|
||||||
post :token, @params_hash
|
post :token, @params_hash
|
||||||
response.body.blank?.should be_false
|
response.body.blank?.should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders something for cubbies ' do
|
it 'renders something for cubbies ' do
|
||||||
prepare_manifest("http://cubbi.es")
|
prepare_manifest("http://cubbi.es/")
|
||||||
@controller.stub!(:verify).and_return('ok')
|
@controller.stub!(:verify).and_return('ok')
|
||||||
post :token, @params_hash
|
post :token, @params_hash
|
||||||
response.body.blank?.should be_false
|
response.body.blank?.should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders something for localhost' do
|
it 'renders something for localhost' do
|
||||||
prepare_manifest("http://localhost:3423")
|
prepare_manifest("http://localhost:3423/")
|
||||||
@controller.stub!(:verify).and_return('ok')
|
@controller.stub!(:verify).and_return('ok')
|
||||||
post :token, @params_hash
|
post :token, @params_hash
|
||||||
response.body.blank?.should be_false
|
response.body.blank?.should be_false
|
||||||
|
|
@ -94,7 +94,7 @@ describe AuthorizationsController do
|
||||||
@controller.stub!(:verify).and_return('ok')
|
@controller.stub!(:verify).and_return('ok')
|
||||||
post :token, @params_hash
|
post :token, @params_hash
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a client application' do
|
it 'creates a client application' do
|
||||||
@controller.stub!(:verify).and_return('ok')
|
@controller.stub!(:verify).and_return('ok')
|
||||||
lambda {
|
lambda {
|
||||||
|
|
@ -108,12 +108,12 @@ describe AuthorizationsController do
|
||||||
post :token, @params_hash
|
post :token, @params_hash
|
||||||
}.should_not change(OAuth2::Provider.client_class, :count)
|
}.should_not change(OAuth2::Provider.client_class, :count)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'verifies the signable string validity(time,nonce,sig)' do
|
it 'verifies the signable string validity(time,nonce,sig)' do
|
||||||
@controller.should_receive(:verify){|a,b,c,d|
|
@controller.should_receive(:verify){|a,b,c,d|
|
||||||
a.should == @signed_string
|
a.should == @signed_string
|
||||||
b.should == @signature
|
b.should == @signature
|
||||||
c.export.should == @public_key.export
|
c.export.should == @public_key.export
|
||||||
d.should == @manifest
|
d.should == @manifest
|
||||||
}
|
}
|
||||||
post :token, @params_hash
|
post :token, @params_hash
|
||||||
|
|
@ -127,8 +127,8 @@ describe AuthorizationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'assigns the auth. & apps for the current user' do
|
it 'assigns the auth. & apps for the current user' do
|
||||||
app1 = Factory.create(:app, :name => "Authorized App")
|
app1 = Factory.create(:app, :name => "Authorized App")
|
||||||
app2 = Factory.create(:app, :name => "Unauthorized App")
|
app2 = Factory.create(:app, :name => "Unauthorized App")
|
||||||
auth = OAuth2::Provider.authorization_class.create(:client => app1, :resource_owner => alice)
|
auth = OAuth2::Provider.authorization_class.create(:client => app1, :resource_owner => alice)
|
||||||
|
|
||||||
OAuth2::Provider.authorization_class.create(:client => app1, :resource_owner => bob)
|
OAuth2::Provider.authorization_class.create(:client => app1, :resource_owner => bob)
|
||||||
|
|
@ -142,13 +142,13 @@ describe AuthorizationsController do
|
||||||
|
|
||||||
describe "#destroy" do
|
describe "#destroy" do
|
||||||
before do
|
before do
|
||||||
@app1 = Factory.create(:app)
|
@app1 = Factory.create(:app)
|
||||||
@auth1 = OAuth2::Provider.authorization_class.create(:client => @app1, :resource_owner => alice)
|
@auth1 = OAuth2::Provider.authorization_class.create(:client => @app1, :resource_owner => alice)
|
||||||
@auth2 = OAuth2::Provider.authorization_class.create(:client => @app1, :resource_owner => bob)
|
@auth2 = OAuth2::Provider.authorization_class.create(:client => @app1, :resource_owner => bob)
|
||||||
end
|
end
|
||||||
it 'deletes an authorization' do
|
it 'deletes an authorization' do
|
||||||
lambda{
|
lambda{
|
||||||
delete :destroy, :id => @app1.id
|
delete :destroy, :id => @app1.id
|
||||||
}.should change(OAuth2::Provider.authorization_class, :count).by(-1)
|
}.should change(OAuth2::Provider.authorization_class, :count).by(-1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -178,9 +178,9 @@ describe AuthorizationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'checks consistency of app_url' do
|
it 'checks consistency of app_url' do
|
||||||
@controller.verify(@signed_string, @sig, @public_key, @manifest.merge({"homepage_url" => "http://badsite.com"})).should == "the app url in the manifest does not match the url passed in the parameters"
|
@controller.verify(@signed_string, @sig, @public_key, @manifest.merge({"application_base_url" => "http://badsite.com/"})).should == "the app url in the manifest does not match the url passed in the parameters"
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'checks key size' do
|
it 'checks key size' do
|
||||||
short_key = RSA.generate(100)
|
short_key = RSA.generate(100)
|
||||||
RSA.stub!(:new).and_return(short_key)
|
RSA.stub!(:new).and_return(short_key)
|
||||||
|
|
@ -219,13 +219,13 @@ describe AuthorizationsController do
|
||||||
describe 'valid_nonce' do
|
describe 'valid_nonce' do
|
||||||
before do
|
before do
|
||||||
@nonce = "abc123"
|
@nonce = "abc123"
|
||||||
Factory.create(:app, :nonce => @nonce)
|
Factory.create(:app, :nonce => @nonce)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns true if its a new nonce' do
|
it 'returns true if its a new nonce' do
|
||||||
@controller.valid_nonce?("lalalala").should be_true
|
@controller.valid_nonce?("lalalala").should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns false if the nonce was already used' do
|
it 'returns false if the nonce was already used' do
|
||||||
@controller.valid_nonce?(@nonce).should be_false
|
@controller.valid_nonce?(@nonce).should be_false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ end
|
||||||
|
|
||||||
Factory.define(:app, :class => OAuth2::Provider.client_class) do |a|
|
Factory.define(:app, :class => OAuth2::Provider.client_class) do |a|
|
||||||
a.sequence(:name) { |token| "Chubbies#{token}" }
|
a.sequence(:name) { |token| "Chubbies#{token}" }
|
||||||
a.sequence(:homepage_url) { |token| "http://chubbi#{token}.es/" }
|
a.sequence(:application_base_url) { |token| "http://chubbi#{token}.es/" }
|
||||||
|
|
||||||
a.description "The best way to chub on the net."
|
a.description "The best way to chub on the net."
|
||||||
a.icon_url "/images/chubbies48.png"
|
a.icon_url "/images/chubbies48.png"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue