google-sa-auth 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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: