sql store for ruby-openid
artemy tregoubenko
atregoubenko at gmail.com
Tue Feb 12 07:47:54 PST 2008
Hi Andrew,
Basing on my own experience, your class will be accepted much faster
if you complete steps described here:
http://openidenabled.com/contribute/
Most important points are sending file as darcs patch and ensuring
your storage passes all tests.
Good luck ; )
On 2/12/08, Andrew Arrow <oneone at gmail.com> wrote:
> I've been running openid in production for a few weeks now and the
> db/cstore/nonce directory has WAY too many files in it. I looked for
> the SQL Store class but couldn't find it so I wrote this one. I tried
> to get really good indexes on the two tables so not only would lookups
> be fast, but all selecting all the expired ones. I used datamapper
> with these models:
>
> class OpenAssociation < DataMapper::Base
> property :proto, :string, :size => 255
> property :domain, :string, :size => 255
> property :url, :string, :size => 255
> property :handle, :string, :size => 255
> property :data, :text
>
> property :valid_til, :datetime, :index => true
>
> index([:proto, :domain, :url, :handle], :unique)
> index([:proto, :domain, :url, :valid_til])
> end
>
> class OpenNonce < DataMapper::Base
> property :proto, :string, :size => 255
> property :domain, :string, :size => 255
> property :url, :string, :size => 255
> property :salt, :string, :size => 255
>
> property :valid_til, :datetime, :index => true
>
> index([:valid_til, :proto, :domain, :url, :salt], :unique)
>
> end
>
> which make tables like:
>
> CREATE TABLE `open_associations` (
> `id` bigint(20) unsigned NOT NULL auto_increment,
> `proto` varchar(255) collate utf8_unicode_ci default NULL,
> `domain` varchar(255) collate utf8_unicode_ci default NULL,
> `url` varchar(255) collate utf8_unicode_ci default NULL,
> `handle` varchar(255) collate utf8_unicode_ci default NULL,
> `data` text collate utf8_unicode_ci,
> `valid_til` datetime default NULL,
> PRIMARY KEY (`id`),
> UNIQUE KEY `open_associations_proto_domain_url_handle_index`
> (`proto`,`domain`,`url`,`handle`),
> KEY `open_associations_valid_til_index` (`valid_til`),
> KEY `open_associations_proto_domain_url_valid_til_index`
> (`proto`,`domain`,`url`,`valid_til`)
> ) ENGINE=InnoDB
>
> CREATE TABLE `open_nonces` (
> `id` bigint(20) unsigned NOT NULL auto_increment,
> `proto` varchar(255) collate utf8_unicode_ci default NULL,
> `domain` varchar(255) collate utf8_unicode_ci default NULL,
> `url` varchar(255) collate utf8_unicode_ci default NULL,
> `salt` varchar(255) collate utf8_unicode_ci default NULL,
> `valid_til` datetime default NULL,
> PRIMARY KEY (`id`),
> UNIQUE KEY `open_nonces_valid_til_proto_domain_url_salt_index`
> (`valid_til`,`proto`,`domain`,`url`,`salt`),
> KEY `open_nonces_valid_til_index` (`valid_til`)
> ) ENGINE=InnoDB
>
> Then I modified the filesystem store like so:
>
> require 'openid/util'
> require 'openid/store/interface'
> require 'openid/store/nonce'
> require 'openid/association'
>
> class SqlStore < OpenID::Store::Interface
> @@FILENAME_ALLOWED =
> "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-".split("")
>
> # store_association(server_url, association)
> # get_association(server_url, handle=nil)
> # remove_association(server_url, handle)
> # use_nonce(server_url, timestamp, salt)
> # cleanup_nonces
> # cleanup_associations
> # cleanup
>
> def get_new_association(server_url, handle)
> unless server_url.index('://')
> raise ArgumentError, "Bad server URL: #{server_url}"
> end
>
> oa = OpenAssociation.new
> oa.proto, rest = server_url.split('://', 2)
> oa.domain = filename_escape(rest.split('/',2)[0])
> oa.url = safe64(server_url)
> if handle
> oa.handle = safe64(handle)
> else
> oa.handle = ''
> end
> oa
> end
>
> def store_association(server_url, association)
> oa = get_new_association(server_url, association.handle)
> oa.data = association.serialize
> oa.valid_til = Time.at(association.issued.to_i + association.lifetime)
> oa.save
> end
>
> def get_association(server_url, handle=nil)
> oa = get_new_association(server_url, handle)
> if handle
> return _get_association(oa)
> end
> sql = %{ SELECT id FROM open_associations WHERE
> proto='#{oa.proto}' and
> domain='#{oa.domain}' and
> url='#{oa.url}' ORDER BY valid_til }
> last = OpenAssociation.find_by_sql(sql).last
> return _get_association(nil, last) if last
> end
>
> def _get_association(oa, id=nil)
>
> if not id
> sql = %{ SELECT id FROM open_associations WHERE
> proto='#{oa.proto}' and
> domain='#{oa.domain}' and
> url='#{oa.url}' and
> handle='#{oa.handle}' }
> id = OpenAssociation.find_by_sql(sql).first
> return if not id
> end
>
> sql = %{ SELECT data FROM open_associations WHERE
> id=#{id} }
> data = OpenAssociation.find_by_sql(sql).first
>
> begin
> association = OpenID::Association.deserialize(data)
> rescue
> database.execute("delete from open_associations where id=#{id}")
> return nil
> end
>
> if association.expires_in == 0
> database.execute("delete from open_associations where id=#{id}")
> return nil
> else
> return association
> end
> end
>
> def remove_association(server_url, handle)
> assoc = get_association(server_url, handle)
>
> if assoc.nil?
> return false
> else
> oa = get_new_association(server_url, handle)
> sql = %{ delete FROM open_associations WHERE
> proto='#{oa.proto}' and
> domain='#{oa.domain}' and
> url='#{oa.url}' and
> handle='#{oa.handle}' }
> database.execute(sql)
> return true
> end
> end
>
> # Return whether the nonce is valid
> def use_nonce(server_url, timestamp, salt)
> return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
>
> on = OpenNonce.new
> if server_url and !server_url.empty?
> on.proto, rest = server_url.split('://',2)
> else
> on.proto, rest = '',''
> end
> raise "Bad server URL" unless on.proto && rest
>
> on.domain = filename_escape(rest.split('/',2)[0])
> on.url = safe64(server_url)
> on.salt = safe64(salt)
> on.valid_til = Time.at(timestamp + OpenID::Nonce.skew)
>
> on.save
> end
>
> # Remove expired entries from the database. This is potentially expensive,
> # so only run when it is acceptable to take time.
> def cleanup
> cleanup_associations
> cleanup_nonces
> end
>
> def cleanup_associations
> sql = %{ delete FROM open_associations WHERE
> valid_til < now() }
> database.execute(sql).affected_rows
> end
>
> def cleanup_nonces
> sql = %{ delete FROM open_nonces WHERE
> valid_til < now() }
> database.execute(sql).affected_rows
> end
>
> protected
>
> # Create a temporary file and return the File object and filename.
> #
> # create a safe filename from a url
> def filename_escape(s)
> s = '' if s.nil?
> filename_chunks = []
> s.split('').each do |c|
> if @@FILENAME_ALLOWED.index(c)
> filename_chunks << c
> else
> filename_chunks << sprintf("_%02X", c[0])
> end
> end
> filename_chunks.join("")
> end
>
> def safe64(s)
> s = OpenID::CryptUtil.sha1(s)
> s = OpenID::Util.to_base64(s)
> s.gsub!('+', '_')
> s.gsub!('/', '.')
> s.gsub!('=', '')
> return s
> end
>
> end
>
> _______________________________________________
> Dev mailing list
> Dev at lists.openidenabled.com
> http://lists.openidenabled.com/mailman/listinfo/dev
>
--
arty ( http://arty.name )
More information about the Dev
mailing list