danski-ooh-auth 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. data/LICENSE +20 -0
  2. data/Rakefile +58 -0
  3. data/app/controllers/application.rb +16 -0
  4. data/app/controllers/authenticating_clients.rb +60 -0
  5. data/app/controllers/tokens.rb +94 -0
  6. data/app/helpers/application_helper.rb +64 -0
  7. data/app/helpers/authenticating_clients_helper.rb +5 -0
  8. data/app/helpers/authentications_helper.rb +5 -0
  9. data/app/models/authenticating_client.rb +12 -0
  10. data/app/models/authenticating_client/dm_authenticating_client.rb +71 -0
  11. data/app/models/token.rb +12 -0
  12. data/app/models/token/dm_token.rb +150 -0
  13. data/app/views/authenticating_clients/_help.html.erb +1 -0
  14. data/app/views/authenticating_clients/edit.html.erb +27 -0
  15. data/app/views/authenticating_clients/index.html.erb +24 -0
  16. data/app/views/authenticating_clients/new.html.erb +47 -0
  17. data/app/views/authenticating_clients/show.html.erb +40 -0
  18. data/app/views/layout/ooh_auth.html.erb +23 -0
  19. data/app/views/tokens/create.html.erb +34 -0
  20. data/app/views/tokens/edit.html.erb +4 -0
  21. data/app/views/tokens/new.html.erb +52 -0
  22. data/app/views/tokens/show.html.erb +1 -0
  23. data/lib/ooh-auth.rb +103 -0
  24. data/lib/ooh-auth/authentication_mixin.rb +13 -0
  25. data/lib/ooh-auth/controller_mixin.rb +38 -0
  26. data/lib/ooh-auth/key_generators.rb +57 -0
  27. data/lib/ooh-auth/merbtasks.rb +103 -0
  28. data/lib/ooh-auth/request_verification_mixin.rb +160 -0
  29. data/lib/ooh-auth/slicetasks.rb +18 -0
  30. data/lib/ooh-auth/spectasks.rb +65 -0
  31. data/lib/ooh-auth/strategies/oauth.rb +16 -0
  32. data/public/javascripts/master.js +0 -0
  33. data/public/stylesheets/master.css +2 -0
  34. data/readme.markdown +43 -0
  35. data/spec/controllers/application_spec.rb +35 -0
  36. data/spec/controllers/authenticating_clients_spec.rb +119 -0
  37. data/spec/controllers/tokens_spec.rb +173 -0
  38. data/spec/merb-auth-slice-fullfat_spec.rb +41 -0
  39. data/spec/models/authenticating_client_spec.rb +44 -0
  40. data/spec/models/oauth_strategy_spec.rb +48 -0
  41. data/spec/models/request_verification_mixin_spec.rb +121 -0
  42. data/spec/models/token_spec.rb +139 -0
  43. data/spec/spec_fixtures.rb +19 -0
  44. data/spec/spec_helper.rb +107 -0
  45. data/stubs/app/controllers/application.rb +2 -0
  46. data/stubs/app/controllers/main.rb +2 -0
  47. metadata +133 -0
@@ -0,0 +1 @@
1
+ <h2>Developer Documentation partial</h2>
@@ -0,0 +1,27 @@
1
+ <h1>Edit your Application</h1>
2
+
3
+ <%= form_for @authenticating_client,
4
+ :action=>slice_url(:authenticating_client, @authenticating_client),
5
+ :method=>"put", :class=>"authenticating_client" do %>
6
+
7
+ <%= error_messages_for @authenticating_client %>
8
+
9
+ <fieldset>
10
+ <legend>Application information</legend>
11
+
12
+ <dl>
13
+ <dt><label for="ooh_auth_authenticating_clients_name">Application name</label></dt>
14
+ <dd><%= text_field :name, :name=>"authenticating_client[name]", :value=>h(@authenticating_client.name) %></dd>
15
+
16
+ <dt><label for="ooh_auth_authenticating_clients_name">Web URL</label></dt>
17
+ <dd><%= text_field :web_url, :name=>"authenticating_client[web_url]", :value=>h(@authenticating_client.web_url) %></dd>
18
+ </dl>
19
+
20
+ </fieldset>
21
+
22
+ <fieldset class="buttons">
23
+ <input type="hidden" name="_method" value="put" />
24
+ <%= submit "Submit changes" %>
25
+ </fieldset>
26
+
27
+ <% end =%>
@@ -0,0 +1,24 @@
1
+ <h1>Developer API</h1>
2
+
3
+ <% if session.user %>
4
+
5
+ <h2>Your Applications</h2>
6
+
7
+ <ul class="authenticating_clients">
8
+ <li class="new"><%= link_to "Register a new Application", slice_url(:new_authenticating_client), :class=>"new" %></li>
9
+ <% @authenticating_clients.each do |ac| %>
10
+ <li>
11
+ <%= link_to h(ac.name), slice_url(:authenticating_client, ac), :class=>"show" %>
12
+ <%= link_to "Edit", slice_url(:edit_authenticating_client, ac), :class=>"edit" %>
13
+ <%= link_to "Unregister", slice_url(:delete_authenticating_client, ac), :class=>"delete" %>
14
+ </li>
15
+ <% end %>
16
+ </ul>
17
+
18
+ <% else %>
19
+
20
+ <p>In order to use the Developer API, you'll need to register for an API key. Please log in to begin the process.</p>
21
+
22
+ <% end %>
23
+
24
+ <%= partial "help" %>
@@ -0,0 +1,47 @@
1
+ <h1>Register for a new API Key</h1>
2
+
3
+ <p>
4
+ <strong>Important!</strong> Upon successfully adding your application, you will be shown two pieces of information.
5
+ You'll be given your <strong>API Key</strong>, which will allow you to interact with the API, and you'll be given a
6
+ <strong>shared secret</strong> which will allow you to verify your requests to the API. <strong>Note both of these down.</strong>
7
+ </p>
8
+
9
+ <%= form_for @authenticating_client, :action=>resource(:ooh_auth, :authenticating_clients), :class=>"authenticating_client" do %>
10
+
11
+ <%= error_messages_for @authenticating_client %>
12
+
13
+ <fieldset>
14
+ <legend>Some information about your application</legend>
15
+
16
+ <dl>
17
+ <dt><label for="ooh_auth_authenticating_clients_name">Application name</label></dt>
18
+ <dd><%= text_field :name, :name=>"authenticating_client[name]", :value=>h(@authenticating_client.name) %></dd>
19
+
20
+ <dt><label for="ooh_auth_authenticating_clients_name">Web URL</label></dt>
21
+ <dd><%= text_field :web_url, :name=>"authenticating_client[web_url]", :value=>h(@authenticating_client.web_url) %></dd>
22
+ </dl>
23
+
24
+ </fieldset>
25
+
26
+ <fieldset>
27
+ <legend>Application type</legend>
28
+
29
+ <dl class="checkboxes">
30
+ <dt><label for="ooh_auth_authenticating_clients_kind_web">This is a web-based application</label></dt>
31
+ <dd><%= radio_button :kind, :value=>"web", :name=>"authenticating_client[kind]", :id=>"ooh_auth_authenticating_clients_kind_web", :checked=>@authenticating_client.is_webapp? %></dd>
32
+
33
+ <dt><label for="ooh_auth_authenticating_clients_kind_desktop">This is a desktop or mobile application</label></dt>
34
+ <dd><%= radio_button :kind, :value=>"desktop", :name=>"authenticating_client[kind]", :id=>"ooh_auth_authenticating_clients_kind_desktop", :checked=>!@authenticating_client.is_webapp? %></dd>
35
+ </dl>
36
+ </fieldset>
37
+
38
+ <fieldset class="buttons">
39
+ <p>
40
+ When you submit this form, we will generate both two pieces of information for you - an <strong>API Key</strong> and a <strong>Shared Secret</strong>.
41
+ They will be shown on the next page. Be sure to record them.
42
+ </p>
43
+
44
+ <%= submit "Get my API Key" %>
45
+ </fieldset>
46
+
47
+ <% end =%>
@@ -0,0 +1,40 @@
1
+ <%
2
+ ac = @authenticating_client
3
+ %>
4
+
5
+ <h1><%=h ac.name %></h1>
6
+
7
+ <div id="facts">
8
+ <h2>About your application:</h2>
9
+
10
+ <ul>
11
+ <li>This application was registered on <%= ac.created_at.strftime("%d/%b/%Y") %></li>
12
+ </ul>
13
+ </div>
14
+
15
+ <div id="api_secrets">
16
+ <h2>Your API key details for <em><%=h ac.name %></em></h2>
17
+ <p>
18
+ Your <strong>Consumer Key</strong> will for the most part be public, although it is useless without the <strong>Consumer Secret</strong> that goes with it.
19
+ You should under no circumstances make your Consumer Secret known by another party, as it can be used to sign the authorization requests that your application
20
+ will send.
21
+ </p>
22
+
23
+ <dl>
24
+ <dt>Your OAuth Consumer Key</dt>
25
+ <dd>
26
+ <a href="#api_key" onclick="this.style.display = 'none'; document.getElementById('api_key').style.display = 'block'; return false;">Show my API Key</a>
27
+ <span id="api_key" class="secret shared" style="display: none;"><%=h ac.api_key %></span>
28
+ </dd>
29
+
30
+ <dt>Your OAuth Consumer Secret</dt>
31
+ <dd>
32
+ <a href="#shared_secret" id="shared_secret_toggle" onclick="this.style.display = 'none'; document.getElementById('shared_secret').style.display = 'block'; return false;">
33
+ Nobody but myself can see. I have closed my doors, shuttered my windows and, just for today, shunned my loved ones. It is safe to show my Consumer Secret.
34
+ </a>
35
+ <span id="shared_secret" class="secret shared" style="display: none;"><%=h ac.secret %></span>
36
+ </dd>
37
+ </dl>
38
+ </div>
39
+
40
+ <%= partial "help" %>
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5
+ <title>OohAuth Slice</title>
6
+ </head>
7
+ <!-- you can override this layout at slices/ooh-auth/app/views/layout/ooh-auth.html.erb -->
8
+ <body class="ooh-auth">
9
+ <div id="root">
10
+ <h1>OohAuth Slice</h1>
11
+
12
+ <% unless message.blank? %>
13
+ <div id="_message">
14
+ <%=h message %>
15
+ </div>
16
+ <% end %>
17
+
18
+ <div id="main">
19
+ <%= catch_content :for_layout %>
20
+ </div>
21
+ </div>
22
+ </body>
23
+ </html>
@@ -0,0 +1,34 @@
1
+ <%
2
+ ac = @authenticating_client
3
+ %>
4
+
5
+ <% if @activated %>
6
+
7
+ <h1 class="win">You successfully authorized <%=h ac.name %></h1>
8
+
9
+ <div id="win facts">
10
+ <h2>To access your account:</h2>
11
+
12
+ <ul>
13
+ <li>Until <%= @token.expires.strftime("%d/%b/%Y") %></li>
14
+ <li>With permission to <%= OohAuth[:client_permission_levels][@token.permissions.to_sym][:able_to] %>.</li>
15
+ </ul>
16
+
17
+ <p>
18
+ <strong>You may now close this window or navigate away from this page.</strong>
19
+ </p>
20
+ </div>
21
+
22
+ <% else %>
23
+
24
+ <h1 class="fail">You denied <%=h ac.name %> access to your content</h1>
25
+
26
+ <div id="fail facts">
27
+ <h2>This application will not be able to access your account.</h2>
28
+
29
+ <p>
30
+ <strong>You may now close this window or navigate away from this page.</strong>
31
+ </p>
32
+ </div>
33
+
34
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <h1>Authentications controller, edit action</h1>
2
+
3
+ <p>Edit this file in <tt>app/views/authentications/edit.html.erb</tt></p>
4
+ <p>For more information and examples of CRUD views read <a href="http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb"> this wiki page</a>
@@ -0,0 +1,52 @@
1
+ <h1><%=h @authenticating_client.name %> wants access to your account!</h1>
2
+
3
+ <p class="abstract">
4
+ The application <%= link_to h(@authenticating_client.name), @authenticating_client.web_url %> wants access to your content.
5
+ </p>
6
+
7
+ <h2>Grant this application access to your account</h2>
8
+ <%= form_for @authenticating_client, :action=>slice_url(:tokens), :class=>"authentication" do %>
9
+ <fieldset>
10
+ <p class="confirmation">
11
+ <%=h @authenticating_client.name %> will be granted access to your data.
12
+ The application will <strong>not</strong> have the ability to grant access to other applications.
13
+ You will be able to revoke this access at a later date if you so choose.
14
+ </p>
15
+
16
+ <input type="hidden" name="oauth_token" value="<%=h @token.token_key %>" />
17
+ <% if request.callback %>
18
+ <input type="hidden" name="oauth_callback" value="<%=h request.callback %>" />
19
+ <% end %>
20
+ </fieldset>
21
+
22
+ <fieldset>
23
+ <legend>Options</legend>
24
+
25
+ <dl>
26
+ <dt><label for="token_expires">Allow access until</label></dt>
27
+ <dd>
28
+ <select name="token[expires]" id="token_expires">
29
+ <option value="2999-12-31">Further notice</option> <!-- or when Philip J. Fry wakes up -->
30
+ <option value="<%= (Date.today + 1.year).strftime("%Y-%m-%d") %>">1 year from now</option>
31
+ <option value="<%= (Date.today + 1.month).strftime("%Y-%m-%d") %>">1 month from now</option>
32
+ <option value="<%= (Date.today + 1.week).strftime("%Y-%m-%d") %>">1 week from now</option>
33
+ <option value="<%= (Date.today + 1.day).strftime("%Y-%m-%d") %>">this time tomorrow</option>
34
+ </select>
35
+ </dd>
36
+
37
+ <dt><label for="token_permissions">Allow this application to</label></dt>
38
+ <dd>
39
+ <select name="token[permissions]" id="token_permissions">
40
+ <% OohAuth[:client_permission_levels].each do |name, opts| %>
41
+ <option value="<%=h name %>"><%=h opts[:able_to] %></option>
42
+ <% end %>
43
+ </select>
44
+ </dd>
45
+ </dl>
46
+ </fieldset>
47
+
48
+ <fieldset class="buttons">
49
+ <%= submit "Grant access", :name=>"commit", :value=>"allow" %>
50
+ <%= submit "Deny access", :name=>"commit", :value=>"deny"%>
51
+ </fieldset>
52
+ <% end =%>
@@ -0,0 +1 @@
1
+ oauth_token=<%= @token.token_key %>&oauth_token_secret=<%= @token.secret %>
data/lib/ooh-auth.rb ADDED
@@ -0,0 +1,103 @@
1
+ if defined?(Merb::Plugins)
2
+
3
+ $:.unshift File.dirname(__FILE__)
4
+
5
+ dependency "merb-action-args"
6
+ dependency 'merb-auth-core'
7
+ dependency 'merb-auth-more'
8
+ dependency 'merb-slices'
9
+ dependency "merb-helpers"
10
+ dependency "merb-assets"
11
+
12
+ Merb::Plugins.add_rakefiles "ooh-auth/merbtasks", "ooh-auth/slicetasks", "ooh-auth/spectasks"
13
+
14
+ # Register the Slice for the current host application
15
+ Merb::Slices::register(__FILE__)
16
+
17
+ # Slice configuration - set this in a before_app_loads callback.
18
+ # By default a Slice uses its own layout, so you can swicht to
19
+ # the main application layout or no layout at all if needed.
20
+ #
21
+ # Configuration options:
22
+ # :layout - the layout to use; defaults to :ooh-auth
23
+ # :mirror - which path component types to use on copy operations; defaults to all.
24
+ Merb::Slices::config[:ooh_auth][:layout] ||= :ooh_auth
25
+ Merb::Slices::config[:ooh_auth].merge!({
26
+ :path_prefix=>"auth",
27
+ # Authenticating clients can ask for a certain level of permissions chosen from a list. You can alter that list below:
28
+ :client_permission_levels => {
29
+ :read=> {:able_to=>"read your content, but not to alter it"},
30
+ :write=> {:able_to=>"both read and make changes to your content, but not to delete it"},
31
+ :delete=> {:able_to=>"read, change, and delete your content"}
32
+ },
33
+ # If no permission level is specifically requested during the auth process, the client will be granted:
34
+ :default_permissions =>"write".freeze,
35
+ # Reserved for now
36
+ :client_kinds=>%w(web desktop)
37
+ })
38
+
39
+ # SliceRestful uses merb-auth-more's configuration options:
40
+ # Merb::Plugins.config[:"merb-auth"][:new_session_param] => key to use when looking up the LOGIN in requests.
41
+ # Merb::Plugins.config[:"merb-auth"][:password_param] => key to use when looking up the PASSWORD in requests.
42
+ # Merb::Authentication.user_class => the class to use when calling #authenticate on your user model.
43
+
44
+
45
+ # All Slice code is expected to be namespaced inside a module
46
+ module OohAuth
47
+
48
+ # Slice metadata
49
+ self.description = "OohAuth is Merb slice that extends merb-auth-more with RESTful authentication"
50
+ self.version = "0.1.2"
51
+ self.author = "Dan Glegg"
52
+ self.identifier = "ooh-auth"
53
+
54
+ # Stub classes loaded hook - runs before LoadClasses BootLoader
55
+ # right after a slice's classes have been loaded internally.
56
+ def self.loaded
57
+ end
58
+
59
+ # Initialization hook - runs before AfterAppLoads BootLoader
60
+ def self.init
61
+ require "ooh-auth/authentication_mixin"
62
+ require "ooh-auth/key_generators"
63
+ require "ooh-auth/request_verification_mixin.rb"
64
+ require "ooh-auth/controller_mixin.rb"
65
+ require "ooh-auth/strategies/oauth.rb"
66
+ Merb::Request.send(:include, OohAuth::Request::VerificationMixin)
67
+ Merb::Controller.send(:include, OohAuth::ControllerMixin)
68
+
69
+ # Register strategies
70
+ Merb::Authentication.register :oauth, "ooh-auth/strategies/oauth.rb"
71
+ Merb::Authentication.activate! :oauth
72
+
73
+ unless OohAuth[:no_default_strategies]
74
+ ::Merb::Authentication.activate!(:default_password_form)
75
+ end
76
+ end
77
+
78
+ # Activation hook - runs after AfterAppLoads BootLoader
79
+ def self.activate
80
+ end
81
+
82
+ # Deactivation hook - triggered by Merb::Slices.deactivate(OohAuth)
83
+ def self.deactivate
84
+ end
85
+
86
+ # Add the following to your app's router to mount SliceRestful at the root:
87
+ # Merb::Router.prepare do
88
+ # slice( :OohAuth, :name_prefix => nil, :path_prefix => "auth", :default_routes => false )
89
+ # end
90
+ def self.setup_router(scope)
91
+ scope.resources :authenticating_clients
92
+ scope.resources :tokens
93
+ scope.default_routes
94
+ end
95
+
96
+ end
97
+
98
+ OohAuth.setup_default_structure!
99
+
100
+ # Add dependencies for other OohAuth classes below. Example:
101
+
102
+
103
+ end
@@ -0,0 +1,13 @@
1
+ module Merb
2
+ class Authentication
3
+
4
+ def store_user(user)
5
+ user.id
6
+ end
7
+
8
+ def fetch_user(session_contents = session[:user])
9
+ Merb::Authentication.user_class.get(session_contents)
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ module OohAuth
2
+ module ControllerMixin
3
+
4
+ private
5
+
6
+ # Raises a NotAcceptable (HTTP 406) unless the request is satisfactorily signed by
7
+ # an authenticating client.
8
+ def ensure_signed
9
+ unless request.signed?
10
+ raise Merb::Controller::NotAcceptable,
11
+ "request improperly signed. Given request: #{request.inspect}, expected signature base string #{request.signature_base_string.inspect} to be encryped with key #{request.signature_secret} resulting in #{request.build_signature}"
12
+ end
13
+ end
14
+
15
+ # Shortcut for ensure_authenticated :with=>[LongPasswordFormClassName].
16
+ # ensures that the request is authenticated via personal form login, and excludes API login.
17
+ def forbid_authentication_with_oauth
18
+ raise Merb::Controller::Unauthenticated if request.oauth_request?
19
+ end
20
+
21
+ # Raises a Forbidden (HTTP 403) unless the request carries the desired authorisation or above.
22
+ # Special notes: permission levels are taken from OohAuth[:client_permission_levels] in order. A call to
23
+ # ensure_authorisation(:write) will by default return true, for example, if the current request is authorised with :write
24
+ # or :delete, as :delete is more powerful than :write.
25
+ # A special level named :root is available. A call to ensure_authorisation with :root as an argument will ensure that the
26
+ # user herself is currently authenticated, rather than one of her authorised agents.
27
+ # ensure_authorisation(:root) is therefore a suitable choice for protecting user profile forms, password change forms and
28
+ # other similar content.
29
+ def ensure_authorisation(level=:root)
30
+ keys = MerbAuthSlicePassword[:client_permission_levels].keys
31
+ raise Merb::Controller::Forbidden unless (
32
+ (level == :root and !request.api_request?) or
33
+ (keys.index(level) > keys.index(authorisation_level))
34
+ )
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,57 @@
1
+ # OohAuth::KeyGenerators
2
+ # ==========================================
3
+ # A set of generators for common password types.
4
+ # Usage:
5
+ # OohAuth::KeyGenerators::<type>.new(length_if_applicable)
6
+ # where <type> is any of:
7
+ # Password: a memorable password such as 254yellowShoes or 869spaceageBiplanes
8
+ # Passphrase: a memorable passphrase made up of [length] words.
9
+ # Alphanum: a gibberish string [length] characters long.
10
+
11
+ module OohAuth
12
+ module KeyGenerators
13
+
14
+ ALPHANUM = (('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a).freeze
15
+ # Want to make an absurdly easy contribution to open source software? Think of more adjectives and nouns that don't
16
+ # result in potentially offensive combinations. but DO result in a wider password namespace.
17
+ # In particular, avoid adjectives like moist, gristly, or veiny.
18
+ # and avoid nouns like penis.
19
+ ADJECTIVES = %w(blue green red orange purple grey yellow scarlet flying edible tasty noisy giant tiny angry great terrific
20
+ improvised tiny magnificent futuristic anachronistic cromulent fashionable trendy spaceage vintage classic
21
+ speedy slow loud quiet
22
+ ).freeze
23
+ NOUNS = %w(ninjas nostrils suitcases earlobes houses cakes pies shoes dinosaurs robots androids antelope bees
24
+ insects chickens apples guitars trombones baloons suitcases pineapples cheeses teeth mice castles
25
+ monsters bicycles kippers turtles bongos words phrases tables desks couches biplanes beans neighbours
26
+ telephones yetis sentries cupboards
27
+ ).freeze
28
+
29
+ # A memorable password based on the pattern XXadjectiveNouns, for example 67blueTeeth or 267noisySandboxes.
30
+ class Password < String
31
+ def self.gen(len=0)
32
+ return "#{rand(999)}#{ADJECTIVES[rand(ADJECTIVES.length)]}#{NOUNS[rand(NOUNS.length)].capitalize}"
33
+ end
34
+ end
35
+
36
+ # A multi-word passphrase based on the dictionary, containing spaces.
37
+ class Passphrase < String
38
+ def self.gen(len=3)
39
+ p = []
40
+ len.times do |i|
41
+ pool = NOUNS.select {|w| !p.include?(w)}
42
+ p << pool[rand(pool.length)]
43
+ end
44
+ return p.join(" ")
45
+ end
46
+ end
47
+
48
+ # A good ol' fashioned incomprehensible string of alphanumeric characters.
49
+ class Alphanum < String
50
+ def self.gen(len=18)
51
+ return((0..(len-1)).collect {|x| ALPHANUM[rand(ALPHANUM.length)] }.join(""))
52
+ end
53
+ end
54
+
55
+
56
+ end # module KeyGenerators
57
+ end # module OohAuth