agree2 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
File without changes
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Extra Eagle LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,165 @@
1
+ = Agree2 Client
2
+
3
+ Agree2 (https://agree2.com) is a site for creating and maintaining contracts.
4
+
5
+ This Client library will let you integrate Agree2 into your own web applications to create full legal contracts between you and your users or between your users.
6
+
7
+ == Install the Agree2 gem
8
+
9
+ The Agree2 client is installed as a ruby gem:
10
+
11
+ sudo gem install agree2
12
+
13
+ The source is available on github https://github.com/pelle/agree2-client/tree/master .
14
+
15
+ == Getting Started
16
+
17
+ First of all you need to signup with https://agree2.com.
18
+
19
+ Now go to the page http://agree2.dev/client_applications and register an application.
20
+
21
+ Find the snippet of code on your client application page called "Example using your own AccessToken". This provides you everything to get started creating agreements under you own account. It should something like this:
22
+
23
+ @client=Agree2::Client.new "DM0VybBbeTc6GfFSF1WbQ","aVmgGmQ1WOjsbbSZbrPcT4qet6IMa2tqJebaeyIBs"
24
+ @user=@client.user "zEHUnKDNuLGXQuicFWZRpQ","0VM3ESRl3rGtg95Wa2JsOFWmu4E78lNHfvMy1j4UtQ"
25
+
26
+ If you wanted to list all the agreements you've got in your account you would do the following:
27
+
28
+ @user.agreements
29
+
30
+ Creating a new agreement:
31
+
32
+ @agreement=@user.agreements.create(:title=>"User Agreement",:body=>"This is the body of the agreement")
33
+ @agreement.to_url # Returns the agreement's url
34
+ redirect_to @agreement.to_url # Redirect to agreements url in Rails
35
+
36
+ Invite a party:
37
+
38
+ @party=@agreement.parties.create :role=>"client",:first_name=>"Joe",:last_name=>"Bloggs",:email=>"joe@blogs.inv",:organization_name=>"Big Inc"
39
+ redirect_to @party.present # Redirect user in an authenticated way to agreement
40
+
41
+ List the parties to an agreement:
42
+
43
+ @agreement.parties
44
+
45
+ By default new agreements are in draft mode, ready for further customization. For the parties to be able to accept the agreement you must finalize it:
46
+
47
+ @agreement.finalize!
48
+
49
+ == Using Templates
50
+
51
+ In most day to day use you will probably be using templates to create agreements. You can create your own templates or use our growing library of public templates (https://agree2.com/masters/public).
52
+
53
+ As an example lets use this "Confidentiality Agreement for access to source code" (https://agree2.com/masters/b4f9a904efaab5ad71f695824c997c332b955876).
54
+
55
+ You could instantiate the template using the long code that comes at the end of the url:
56
+
57
+ @template=@agree2_user.templates.find "b4f9a904efaab5ad71f695824c997c332b955876"
58
+
59
+ We have actually made it even easier though. Each template has it's own Ruby version that you can download. If you click on "Tools" and then "Instant Ruby API" you will find it customized for your own use. In our example you can find it at:
60
+
61
+ https://agree2.com/masters/b4f9a904efaab5ad71f695824c997c332b955876.rb
62
+
63
+ Save this file and require it into your application. This will also provide you with an instance of the template in @template.
64
+
65
+ === Preparing an Agreement from a Template
66
+
67
+ To create an agreement with no customizations simply do:
68
+
69
+ @agreement=@template.prepare
70
+
71
+ Templates have user defined fields that can be filled out by the application. This is where it gets interesting:
72
+
73
+ @agreement=@template.prepare :holder => "John Doe",
74
+ :holder_info => "john@gmail.inv",
75
+ :coder => "Phil Armonic",
76
+ :coder_info => "phil@gmail.inv",
77
+ :project => "Spoogle.com",
78
+ :svn => "http://svnhost.inv/spoogle"
79
+
80
+ Each of the custom fields from the template is now directly available in the agreement:
81
+
82
+ puts @agreement.holder
83
+ > "John Doe"
84
+
85
+ You can also get the full list of fields using:
86
+
87
+ @agreement.fields
88
+
89
+ === Preparing Agreement from a Template and adding Parties
90
+
91
+ To reduce the amount of network activity between your servers and ours you can create an agreement from a template and add parties in one step.
92
+
93
+ @agreement=@template.prepare {
94
+ :holder => "John Doe",
95
+ :holder_info => "john@gmail.inv",
96
+ :coder => "Phil Armonic",
97
+ :coder_info => "phil@gmail.inv",
98
+ :project => "Spoogle.com",
99
+ :svn => "http://svnhost.inv/spoogle"
100
+ },{
101
+ :coder=>{
102
+ :first_name=>"Phil",
103
+ :last_name=>"Armonic",
104
+ :email=>"phil@gmail.inv"
105
+ },
106
+ :holder=>{
107
+ :first_name=>"John",
108
+ :last_name=>"Doe",
109
+ :email=>"john@gmail.inv",
110
+ :organization_name=>"Spoogle Inc"
111
+ }
112
+ }
113
+
114
+ The above creates the agreement, adds the 2 parties and sends emails to them.
115
+
116
+ You can also add your applications user in agree2 as a party without having to repeat all the information:
117
+
118
+ @agreement=@template.prepare {
119
+ :holder => "John Doe",
120
+ :holder_info => "john@gmail.inv",
121
+ :coder => "Phil Armonic",
122
+ :coder_info => "phil@gmail.inv",
123
+ :project => "Spoogle.com",
124
+ :svn => "http://svnhost.inv/spoogle"
125
+ },{
126
+ :coder=>{
127
+ :first_name=>"Phil",
128
+ :last_name=>"Armonic",
129
+ :email=>"phil@gmail.inv"
130
+ }
131
+ },"holder"
132
+
133
+ This does the same as the first example except "holder" will have the details from your user account.
134
+
135
+ === Preparing and signing an agreeement from the API
136
+
137
+ Lets say you want to create an agreement, add the parties and sign it so your user can go straight to it to accept it. Agree2 offers the possibility of signing an agreement during the creation process. Not all accounts have access to this feature.
138
+
139
+ The signing process is done using the OAuth protocol we use for authentication. We also only allow you to sign on behalf of the application's user.
140
+
141
+ @agreement=@template.prepare_and_sign {
142
+ :holder => "John Doe",
143
+ :holder_info => "john@gmail.inv",
144
+ :coder => "Phil Armonic",
145
+ :coder_info => "phil@gmail.inv",
146
+ :project => "Spoogle.com",
147
+ :svn => "http://svnhost.inv/spoogle"
148
+ },{
149
+ :coder=>{
150
+ :first_name=>"Phil",
151
+ :last_name=>"Armonic",
152
+ :email=>"phil@gmail.inv"
153
+ }
154
+ },"holder"
155
+
156
+ The final parameter "holder" is optional. It will add you as a party with the role "application" by default.
157
+
158
+ == About Agree2 Client
159
+
160
+ Author:: Pelle Braendgaard (http://stakeventures.com)
161
+ Copyright:: Copyright (c) 2008 Extra Eagle LLC
162
+ License:: MIT
163
+ Git:: https://github.com/pelle/agree2-client/tree/master
164
+
165
+ This client library allows you to integrate Agree2 into your application.
data/Rakefile ADDED
@@ -0,0 +1,97 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+ require 'rake/testtask'
5
+ require 'spec/rake/spectask'
6
+ require 'rake/gempackagetask'
7
+ require 'yaml'
8
+ namespace :rdoc do |ns|
9
+ Rake::RDocTask.new(:doc) do |rd|
10
+ rd.main = "README.rdoc"
11
+ rd.rdoc_dir = 'doc'
12
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
13
+ rd.options << "-t Agree2"
14
+ end
15
+
16
+ desc 'Publish RDoc to RubyForge.'
17
+ task :publish_docs => [:doc] do
18
+ config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
19
+ host = "#{config["username"]}@rubyforge.org"
20
+
21
+ remote_dir = "/var/www/gforge-projects/agree2"
22
+ local_dir = 'doc'
23
+
24
+ sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}}
25
+ end
26
+
27
+ end
28
+
29
+ namespace :spec do |ns|
30
+ desc "Run all examples"
31
+ Spec::Rake::SpecTask.new('spec') do |t|
32
+ t.spec_files = FileList['spec/*.rb']
33
+ end
34
+
35
+ desc "Run rcov"
36
+ Spec::Rake::SpecTask.new('rcov') do |t|
37
+ t.spec_files = FileList['spec/*.rb']
38
+ t.rcov = true
39
+ t.rcov_opts = ['--exclude','gems,spec']
40
+ end
41
+ end
42
+
43
+ namespace :gem do |gem|
44
+ File.open( 'agree2.gemspec') do |f|
45
+ @gem=eval(f.read)
46
+ @version=@gem.version.to_s
47
+ end
48
+
49
+ Rake::GemPackageTask.new @gem do |pkg|
50
+ pkg.need_tar = true
51
+ pkg.need_zip = true
52
+ end
53
+
54
+ desc 'Install the package as a gem.'
55
+ task :install => [:clean, :package] do
56
+ gem = Dir['pkg/*.gem'].first
57
+ sh "sudo gem install --local #{gem}"
58
+ end
59
+
60
+
61
+ desc 'Package and upload the release to rubyforge.'
62
+ task :release => [:clean, :package] do |t|
63
+ pkg = "pkg/#{name}-#{@version}"
64
+
65
+ if $DEBUG then
66
+ puts "release_id = rf.add_release #{rubyforge_name.inspect}, #{name.inspect}, #{@version.inspect}, \"#{pkg}.tgz\""
67
+ puts "rf.add_file #{rubyforge_name.inspect}, #{name.inspect}, release_id, \"#{pkg}.gem\""
68
+ end
69
+
70
+ rf = RubyForge.new.configure
71
+ puts "Logging in"
72
+ rf.login
73
+
74
+ # c = rf.userconfig
75
+ # c["release_notes"] = description if description
76
+ # c["release_changes"] = changes if changes
77
+ # c["preformatted"] = true
78
+
79
+ files = ["#{pkg}.tgz",
80
+ "#{pkg}.zip",
81
+ "#{pkg}.gem"].compact
82
+
83
+ puts "Releasing #{name} v. #{@version}"
84
+ rf.add_release rubyforge_name, name, @version, *files
85
+ end
86
+
87
+ end
88
+
89
+ desc "Clean up all dirt"
90
+ task :clean => [ "rdoc:clobber_doc", "gem:clobber_package" ] do
91
+ %w(diff diff.txt email.txt ri *.gem *~ **/*~ *.rbc **/*.rbc coverage).each do |pattern|
92
+ files = Dir[pattern]
93
+ rm_rf files, :verbose => true unless files.empty?
94
+ end
95
+ end
96
+
97
+ task :default=>[:spec]
@@ -0,0 +1,71 @@
1
+ module Agree2
2
+ class Agreement<Base
3
+ attr_serializable :permalink,:title,:body,:created_at,:updated_at,:smart_fields,:state,:active_version,
4
+ :version,:digest,:finalized_at,:finalized_at,:terminated_at,:activated_at,:valid_to
5
+
6
+ alias_method :fields,:smart_fields
7
+ # Returns the parties to the agreement
8
+ def parties
9
+ @parties||=Agree2::ProxyCollection.new self,"#{self.path}/parties",'Party'
10
+ end
11
+
12
+ def parties=(values)
13
+ @parties=Agree2::ProxyCollection.new self,"#{self.path}/parties",'Party',values
14
+ end
15
+
16
+ def to_param #:nodoc:
17
+ permalink
18
+ end
19
+
20
+ def respond_to?(symbol, include_priv = false) #:nodoc:
21
+ return true if super symbol,include_priv
22
+ return false if fields.nil?||fields.empty?
23
+ method=symbol.to_s
24
+ if method=~/(.+)=$/
25
+ field=$1
26
+ setter=true
27
+ else
28
+ field=method
29
+ setter=false
30
+ end
31
+ fields.has_key?(field)
32
+ end
33
+
34
+ # Finalize marks a draft agreement as being ready to accept
35
+ def finalize!
36
+ user.post(path+"/finalize")==" "
37
+ end
38
+
39
+ protected
40
+
41
+ def attributes_for_save #:nodoc:
42
+ if new_record?
43
+ {self.class.singular_name=>attributes}
44
+ else
45
+ {"fields"=>fields}
46
+ end
47
+ end
48
+
49
+ def method_missing(method, *args, &block) #:nodoc:
50
+ return super(method, *args, &block) if fields.nil?||fields.empty?
51
+ method=method.to_s
52
+ if method=~/(.+)=$/
53
+ field=$1
54
+ setter=true
55
+ else
56
+ field=method
57
+ setter=false
58
+ end
59
+ if fields.has_key?(field)
60
+ if setter
61
+ fields[field]=args.first
62
+ else
63
+ fields[field]
64
+ end
65
+ else
66
+ super method, *args, &block
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,147 @@
1
+ require 'json'
2
+ require 'set'
3
+ module Agree2
4
+ # The superclass of all Agree2 Resource objects.
5
+ class Base
6
+ class<<self
7
+
8
+ def attr_serializable(*attributes) #:nodoc:
9
+ attributes.map!{|a|a.to_sym}
10
+ write_inheritable_attribute("serializable_attributes",
11
+ Set.new(attributes) +
12
+ (serializable_attributes || []))
13
+ attr_accessor *attributes
14
+ end
15
+
16
+ # Returns an array of all the attributes that have been made accessible to mass-assignment.
17
+ def serializable_attributes # :nodoc:
18
+ read_inheritable_attribute("serializable_attributes")
19
+ end
20
+
21
+ def collection_path #:nodoc:
22
+ "/#{collection_name}"
23
+ end
24
+
25
+ def instance_path(id) #:nodoc:
26
+ "#{collection_path}/#{id}"
27
+ end
28
+
29
+ def collection_name #:nodoc:
30
+ self.to_s.demodulize.tableize
31
+ end
32
+
33
+ def singular_name #:nodoc:
34
+ self.to_s.demodulize.underscore.singularize
35
+ end
36
+
37
+ # Gets an instance of a resource
38
+ def get(container,id)
39
+ user=(container.is_a?(User) ? container : container.user)
40
+ new( container, user.get(container.path+instance_path(id)))
41
+ end
42
+
43
+ end
44
+
45
+ attr_accessor :user,:container
46
+
47
+ def initialize(container,fields={})#:nodoc:
48
+ @container=container
49
+ @user=(container.is_a?(User) ? container : container.user)
50
+ if fields.is_a?(Hash)
51
+ load_attributes(fields)
52
+ else
53
+ load_json(fields)
54
+ end
55
+ end
56
+
57
+ # Has this record been saved to the server yet?
58
+ def new_record?
59
+ @from_wire!=true
60
+ end
61
+
62
+ # Reloads the object from Agree2's servers
63
+ def reload
64
+ load_json(user.get(path))
65
+ end
66
+
67
+ # Destroys the object from Agree2's servers
68
+ def destroy
69
+ user.delete(path)
70
+ end
71
+
72
+ # Returns the full URL for the object
73
+ def to_url
74
+ "#{AGREE2_URL}#{path}"
75
+ end
76
+
77
+ # Returns the relative path to the object
78
+ def path #:nodoc:
79
+ self.container.path+self.class.instance_path(to_param)
80
+ end
81
+
82
+ # The primary key of the object
83
+ def to_param #:nodoc:
84
+ id
85
+ end
86
+
87
+ # Saves the record to the server
88
+ def save
89
+ if new_record?
90
+ load_json(@user.post("#{self.container.path}/#{self.class.collection_name}",attributes_for_save))
91
+ else
92
+ load_json(@user.put("#{path}",attributes_for_save))
93
+ end
94
+ end
95
+
96
+ # Get the primary attributes of an object as a hash
97
+ def attributes
98
+ self.class.serializable_attributes.inject({}) do |h,field|
99
+ value=self.send(field)
100
+ h[field]=value if value
101
+ h
102
+ end
103
+ end
104
+
105
+ protected
106
+
107
+ def attributes_for_save #:nodoc:
108
+ {self.class.singular_name=>attributes}
109
+ end
110
+
111
+ def decode(element) #:nodoc:
112
+ for field in self.class.serializable_attributes
113
+ method_name="#{field.to_s}=".to_sym
114
+ self.send(method_name, (element/field.to_sym).innerHTML) if self.respond_to?(method_name)
115
+ end
116
+ self
117
+ end
118
+
119
+ def load_attributes(attributes) #:nodoc:
120
+ @attributes=attributes
121
+ attributes.each_pair do |key,value|
122
+ method_name="#{key.to_s}=".to_sym
123
+ self.send(method_name,value) if self.respond_to?(method_name)
124
+ end
125
+ end
126
+
127
+ def load_json(json) #:nodoc:
128
+ @from_wire=true
129
+ load_attributes(JSON.parse(json))
130
+ end
131
+ # private
132
+ #
133
+ # def method_missing(method_symbol, *arguments) #:nodoc:
134
+ # method_name = method_symbol.to_s
135
+ #
136
+ # case method_name.last
137
+ # when "="
138
+ # self.attributes[method_name.first(-1)] = arguments.first
139
+ # when "?"
140
+ # self.attributes[method_name.first(-1)]
141
+ # else
142
+ # self.attributes.has_key?(method_name) ? self.attributes[method_name] : super
143
+ # end
144
+ # end
145
+ #
146
+ end
147
+ end
@@ -0,0 +1,101 @@
1
+ require 'oauth/consumer'
2
+
3
+ module Agree2
4
+ # If you haven't done so already register your application at https://agree2.com/client_applications
5
+ #
6
+ # The full authorization process works like this over 2 controller actions (This example is in Rails):
7
+ #
8
+ # def request_token
9
+ # @client = Agree2::Client.new "key","secret"
10
+ # @request_token = @client.get_request_token
11
+ #
12
+ # # Store the token in a model in your applications mapping it to your user
13
+ # Agree2RequestToken.create :user=>current_user,:token=>@request_token.token,:secret=>@request_token.secret
14
+ # redirect_to @request_token.authorize_url
15
+ # end
16
+ #
17
+ # # The user authorizes the token on the Agree2 web site and is redirected to the authorize_url you setup when you registered your application at:
18
+ # # https://agree2.com/client_applications
19
+ # def authorize
20
+ # @client = Agree2::Client.new "key","secret"
21
+ #
22
+ # # Load the request token from your Agree2RequestToken through your user model
23
+ # @request_token = current_user.agree2_request_token.request_token
24
+ #
25
+ # # Exchange the authorized request token for a more permanent User token on the Agree2 site
26
+ # @user_client=@client.user_from_request_token(@request_token)
27
+ #
28
+ # # Store the Agree2 user data in your own model
29
+ # @agree2_user=Agree2User.create :user=>current_user,:token=>@user_client.token,:secret=>@user_client.secret
30
+ # end
31
+ #
32
+
33
+ class Client
34
+ attr_accessor :consumer
35
+
36
+ # Initialize the Agree2 Client
37
+ #
38
+ # === Required Fields
39
+ #
40
+ # * <tt>key</tt> - The Consumer Key
41
+ # * <tt>secret</tt> - The Consumer Secret
42
+ #
43
+ # To get these register your application at: https://agree2.com/client_applications
44
+ def initialize(key,secret)
45
+ @consumer=OAuth::Consumer.new(key,secret,{:site=>AGREE2_URL})
46
+ end
47
+
48
+ # initialize a new user object with the given token and secret. The user object is what you use to do most of the work.
49
+ # Use this method when you need to create a user object from a token and secret you have stored in your applications database.
50
+ #
51
+ # === Required Fields
52
+ #
53
+ # * <tt>token</tt> - This is the authorized Token
54
+ # * <tt>secret</tt> - This is the authorized Token Secret
55
+ #
56
+ def user(token,token_secret)
57
+ User.new(self,token,token_secret)
58
+ end
59
+
60
+ # Start the process of authorizing a token for an Agree2 user.
61
+ #
62
+ # Example:
63
+ # @request_token = @client.get_request_token
64
+ # redirect_to @request_token.authorize_url
65
+ #
66
+ def get_request_token
67
+ consumer.get_request_token
68
+ end
69
+
70
+ # Exchange an Authorized RequestToken for a working user object
71
+ #
72
+ # === Required Field
73
+ #
74
+ # * <tt>request_token</tt> The Request token created using get_request_token and authorized on Agree2 by your user
75
+ #
76
+ # Example:
77
+ #
78
+ # @user_client = @client.user_from_request_token(@request_token)
79
+ #
80
+ def user_from_request_token(request_token)
81
+ access_token=request_token.get_access_token
82
+ user(access_token.token,access_token.secret)
83
+ rescue Net::HTTPServerException=>e
84
+ if e.response.code=='401'
85
+ raise Agree2Exception,"The user has not authorized this request token",caller
86
+ else
87
+ raise
88
+ end
89
+ end
90
+
91
+ # Returns your consumer key
92
+ def key
93
+ @consumer.key
94
+ end
95
+
96
+ # Returns your consumer secret
97
+ def secret
98
+ @consumer.secret
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,23 @@
1
+ module Agree2
2
+ class Party<Base
3
+ attr_serializable :id,:role,:email,:first_name,:last_name,:created_at,:updated_at,:organization_name
4
+ alias_method :agreement,:container
5
+
6
+ # Creates a one time signed url to redirect your user to their acceptance page. This url is only valid once. Call again to
7
+ # redirect your user to the agreement again.
8
+ def present
9
+ path="/present/#{agreement.permalink}/to/#{email}"
10
+ AGREE2_URL+user.client.consumer.create_signed_request(:get,path,user.access_token,{:scheme=>:query_string}).path
11
+ end
12
+
13
+ def self.validate_parties_hash(parties) #:nodoc:
14
+ parties&&parties.each{|r,p| validate_party_hash(p)}
15
+ true
16
+ end
17
+
18
+ def self.validate_party_hash(p) #:nodoc:
19
+ raise ArgumentError,"Your parties are missing required fields" if [:first_name,:last_name,:email].find{|k| !p.include?(k)}
20
+ return true
21
+ end
22
+ end
23
+ end