google-sa-auth 0.0.5

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.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in google-jwt.gemspec
4
+ gemspec
@@ -0,0 +1,4 @@
1
+ google-sa-auth
2
+ ==============
3
+
4
+ Ruby library for obtaining OAuth 2.0 authorization tokens for Google API service accounts.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "google-sa-auth"
6
+ s.version = "0.0.5"
7
+ s.authors = ["Jon Durbin"]
8
+ s.email = ["jond@greenviewdata.com"]
9
+ s.homepage = "https://github.com/gdi/google-sa-auth"
10
+ s.summary = %q{Simple gem for generating authorization tokens for google service accounts}
11
+ s.description = %q{Simple gem for generating authorization tokens for google service accounts}
12
+
13
+ s.rubyforge_project = "google-sa-auth"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency 'rspec'
21
+ s.add_dependency 'bundler'
22
+ s.add_dependency 'curb-fu'
23
+ s.add_dependency 'google-jwt'
24
+ s.add_dependency 'json'
25
+ end
@@ -0,0 +1,88 @@
1
+ require 'google-jwt'
2
+ require 'curb-fu'
3
+ require 'google-sa-auth/scope'
4
+ require 'google-sa-auth/token'
5
+ require 'google-sa-auth/client'
6
+
7
+ class GoogleSAAuth
8
+ attr_accessor :claim_set, :pkcs12, :token
9
+ def initialize(args)
10
+ # Symbolize keys.
11
+ args = args.inject({}){|item,(k,v)| item[k.to_sym] = v; item}
12
+
13
+ # Remove unknown keys and make sure we have all the required keys.
14
+ args.delete_if {|k,v| ![:email_address, :scope, :key, :password, :audience].include?(k)}
15
+ [:email_address, :scope, :key].each do |required|
16
+ raise RuntimeError, "Missing required argument key #{required}" unless args[required]
17
+ end
18
+
19
+ # Setup default password and audience.
20
+ args[:password] ||= 'notasecret'
21
+ args[:audience] ||= 'https://accounts.google.com/o/oauth2/token'
22
+
23
+ # Create the claim set.
24
+ self.claim_set = {:iss => args[:email_address], :aud => args[:audience]}
25
+
26
+ # Determine the scope.
27
+ if args[:scope].class == String
28
+ if args[:scope] =~ /^http/i
29
+ self.claim_set[:scope] = args[:scope]
30
+ else
31
+ self.claim_set[:scope] = GoogleSAAuth::Scope.new(args[:scope]).url
32
+ end
33
+ elsif args[:scope].class == Array
34
+ scopes = args[:scope].each.collect do |scope|
35
+ if scope =~ /http/i
36
+ scope
37
+ else
38
+ GoogleSAAuth::Scope.new(scope).url
39
+ end
40
+ end
41
+ self.claim_set[:scope] = scopes.join(' ')
42
+ elsif args[:scope].class == Hash
43
+ # Specify extension, e.g. => {:fusiontables => 'readonly'}
44
+ scopes = []
45
+ args[:scope].each do |scope,extension|
46
+ if scope =~ /^http/i
47
+ url = scope
48
+ else
49
+ url = GoogleSAAuth::Scope.new(scope).by_extension(extension)
50
+ end
51
+ scopes.push(url) unless url.nil?
52
+ end
53
+ self.claim_set[:scope] = scopes.join(' ')
54
+ end
55
+
56
+ # Set other attributes.
57
+ self.pkcs12 = {:key => args[:key], :password => args[:password]}
58
+
59
+ # Get our authorization.
60
+ auth_token
61
+ self
62
+ end
63
+
64
+ def auth_token
65
+ # Make sure this token isn't expired.
66
+ self.token = nil if self.token.nil? || self.token.expired?
67
+ self.token ||= GoogleSAAuth::Token.new(jwt)
68
+ self.token
69
+ end
70
+
71
+ def token_string
72
+ # Return only the string for the token.
73
+ auth_token.token
74
+ end
75
+
76
+ def jwt
77
+ # Make sure the jwt isn't already expired.
78
+ @json_web_token = nil if @json_web_token.nil? || Time.now.to_i >= @json_web_token.claim_set[:exp]
79
+
80
+ # (Re)create the JSON web token.
81
+ @json_web_token ||= GoogleJWT.new(
82
+ self.claim_set,
83
+ self.pkcs12[:key],
84
+ self.pkcs12[:password]
85
+ )
86
+ @json_web_token
87
+ end
88
+ end
@@ -0,0 +1,43 @@
1
+ class GoogleSAAuth
2
+ class Client
3
+ class << self
4
+ def run(args)
5
+ # Clean up our arguments.
6
+ args = args.inject({}){|item,(k,v)| item[k.to_sym] = v; item}
7
+ args = args.delete_if {|k,v| ![:uri, :data, :headers, :method].include?(k)}
8
+ args[:data] ||= {}
9
+ args[:headers] ||= {}
10
+
11
+ # Parse the uri.
12
+ protocol, host, path = parse_uri(args[:uri])
13
+
14
+ # Set up a nice host info hash.
15
+ host_info = {
16
+ :host => host,
17
+ :path => path,
18
+ :protocol => protocol,
19
+ :headers => args[:headers]
20
+ }
21
+
22
+ # Perform the request.
23
+ if args[:method] == 'get'
24
+ CurbFu.get(host_info, args[:data])
25
+ elsif args[:method] == 'post'
26
+ CurbFu.post(host_info, args[:data])
27
+ elsif args[:method] == 'put'
28
+ CurbFu.put(host_info, args[:data])
29
+ elsif args[:method] == 'delete'
30
+ CurbFu.delete(host_info, args[:data])
31
+ else
32
+ raise 'InvalidRequestType', "Unknown method: #{method}"
33
+ end
34
+ end
35
+
36
+ private
37
+ def parse_uri(uri)
38
+ return [$1, $2, $3] if uri =~ /^(https?):\/\/([a-z0-9\.\-]+)(\/.*)$/i
39
+ raise ArgumentError "Error parsing URI: #{uri}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,78 @@
1
+ class GoogleSAAuth
2
+ class Scope
3
+ $KNOWN_APIS = {}
4
+
5
+ attr_accessor :scope_urls, :api_info
6
+ def initialize(name)
7
+ get_api_info(name.to_s)
8
+ end
9
+
10
+ def url
11
+ full_access
12
+ end
13
+
14
+ def full_access
15
+ # Get the shortest scope, which theoretically will be full permissions.
16
+ self.scope_urls.sort_by {|url| url.length}.first
17
+ end
18
+
19
+ def read_only
20
+ # Simply wrapper for .readonly permissions.
21
+ by_extension('readonly')
22
+ end
23
+
24
+ def by_extension(extension)
25
+ # If extension is nil, then we can just return full access.
26
+ return full_access if extension.nil?
27
+
28
+ # Try to find a scope by extension, e.g. ".readonly"
29
+ self.scope_urls.each do |url|
30
+ return url if url =~ /\.#{extension}$/i
31
+ end
32
+ nil
33
+ end
34
+
35
+ private
36
+ def known_apis
37
+ return $KNOWN_APIS unless $KNOWN_APIS.empty?
38
+
39
+ # Get the list of known APIs using the Google's API Discovery
40
+ response = GoogleSAAuth::Client.run(
41
+ :uri => 'https://www.googleapis.com/discovery/v1/apis',
42
+ :method => 'get'
43
+ )
44
+
45
+ # Make sure we got a 200 response.
46
+ raise RuntimeError unless response.status == 200
47
+
48
+ # Parse the json and store the known apis.
49
+ result = JSON.parse(response.body)
50
+ apis = {}
51
+ result['items'].each {|item| apis[item['name']] = item}
52
+ $KNOWN_APIS = apis
53
+ apis
54
+ end
55
+
56
+ def get_api_info(scope_name)
57
+ return if self.scope_urls
58
+
59
+ # Make sure google listed this API.
60
+ api_info = known_apis[scope_name]
61
+ return nil unless api_info && api_info['discoveryRestUrl']
62
+
63
+ # Get the OAuth 2.0 scope url.
64
+ response = GoogleSAAuth::Client.run(
65
+ :uri => api_info['discoveryRestUrl'],
66
+ :method => 'get'
67
+ )
68
+
69
+ # Make sure we got a 200 response.
70
+ raise RuntimeError unless response.status == 200
71
+
72
+ # Parse the result and try to determine scope URLs.
73
+ result = JSON.parse(response.body)
74
+ self.scope_urls = result['auth']['oauth2']['scopes'].keys rescue nil
75
+ self.api_info = result
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,34 @@
1
+ class GoogleSAAuth
2
+ class Token
3
+ attr_accessor :token, :response, :expires_at
4
+ def initialize(jwt)
5
+ # Get the token upon initialization and symbolize the keys.
6
+ self.response = get_auth_token(jwt.jwt).inject({}){|item,(k,v)| item[k.to_sym] = v; item}
7
+ self.token = self.response[:access_token]
8
+ self.expires_at = jwt.claim_set[:exp]
9
+ end
10
+
11
+ def expired?
12
+ Time.now.to_i >= self.expires_at
13
+ end
14
+
15
+ private
16
+ def get_auth_token(jwt)
17
+ # Post to the google oauth2 token URL.
18
+ result = GoogleSAAuth::Client.run(
19
+ :uri => 'https://accounts.google.com/o/oauth2/token',
20
+ :headers => {
21
+ 'Content-Type' => 'application/x-www-form-urlencoded'
22
+ },
23
+ :data => "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=#{jwt}",
24
+ :method => 'post'
25
+ )
26
+
27
+ # Throw an exception unless we got a 200 response.
28
+ raise RuntimeError, "Error getting authentication token: #{result.body}" unless result.status == 200
29
+
30
+ # Parse the results.
31
+ JSON.parse(result.body.to_s)
32
+ end
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: google-sa-auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jon Durbin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: curb-fu
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: google-jwt
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: json
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Simple gem for generating authorization tokens for google service accounts
95
+ email:
96
+ - jond@greenviewdata.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - Gemfile
102
+ - README.md
103
+ - Rakefile
104
+ - google-sa-auth.gemspec
105
+ - lib/google-sa-auth.rb
106
+ - lib/google-sa-auth/client.rb
107
+ - lib/google-sa-auth/scope.rb
108
+ - lib/google-sa-auth/token.rb
109
+ homepage: https://github.com/gdi/google-sa-auth
110
+ licenses: []
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project: google-sa-auth
129
+ rubygems_version: 1.8.24
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: Simple gem for generating authorization tokens for google service accounts
133
+ test_files: []
134
+ has_rdoc: