motionauth-oauth2 1.0.0
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.
- checksums.yaml +7 -0
- data/README.md +53 -0
- data/lib/oauth2-cocoa/connection.rb +158 -0
- data/lib/oauth2-cocoa/mac_token.rb +58 -0
- data/lib/oauth2-cocoa/response.rb +34 -0
- data/lib/oauth2-cocoa/strategy/assertion.rb +37 -0
- data/lib/oauth2-cocoa/strategy/client_credentials.rb +18 -0
- data/lib/oauth2-cocoa/utils.rb +63 -0
- data/lib/oauth2.rb +23 -0
- data/lib/oauth2/access_token.rb +177 -0
- data/lib/oauth2/client.rb +163 -0
- data/lib/oauth2/connection.rb +35 -0
- data/lib/oauth2/error.rb +24 -0
- data/lib/oauth2/mac_token.rb +74 -0
- data/lib/oauth2/response.rb +58 -0
- data/lib/oauth2/strategy/assertion.rb +59 -0
- data/lib/oauth2/strategy/auth_code.rb +33 -0
- data/lib/oauth2/strategy/base.rb +16 -0
- data/lib/oauth2/strategy/client_credentials.rb +30 -0
- data/lib/oauth2/strategy/implicit.rb +29 -0
- data/lib/oauth2/strategy/password.rb +29 -0
- data/lib/oauth2/version.rb +15 -0
- metadata +197 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 33ca9a665850734bf3aac09d9f7ad5012fa46e07
|
4
|
+
data.tar.gz: 6d20804fc4e3fe933d710b4c53c6a239784e12e3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 626b2c6d29b18a64cc1df408dc3d3257918b22d4501eb41a8aee016bff09303822227b4261aa10cc46515af759ddc0af2226e437e51354d9ab50da207ec70e18
|
7
|
+
data.tar.gz: df77baee7c62d81b5e2a700aa830493e7657fb9d7b80cbf8f13eaa4154662a2dc835bd39fedd4323a9c16fc16fafe3ccf57be58d0c244a4111175565e1a88699
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# MotionAuth-OAuth2
|
2
|
+
|
3
|
+
[](https://travis-ci.org/motionauth/oauth2)
|
4
|
+
[](https://codeclimate.com/github/motionauth/oauth2)
|
5
|
+
|
6
|
+
A [RubyMotion](http://www.rubymotion.com) fork of the existing
|
7
|
+
[OAuth2](https://github.com/intridea/oauth2) RubyGem that works for iOS and OS X.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem "motionauth-oauth2"
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
$ bundle
|
21
|
+
```
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
$ gem install motionauth-oauth2
|
27
|
+
```
|
28
|
+
|
29
|
+
## Usage Examples
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org")
|
33
|
+
|
34
|
+
client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
|
35
|
+
# => "https://example.org/oauth/authorization?response_type=code&client_id=client_id&redirect_uri=http://localhost:8080/oauth2/callback"
|
36
|
+
|
37
|
+
token = client.auth_code.get_token(
|
38
|
+
"authorization_code_value",
|
39
|
+
redirect_uri: "http://localhost:8080/oauth2/callback",
|
40
|
+
headers: { "Authorization" => "Basic some_password" }
|
41
|
+
)
|
42
|
+
response = token.get("/api/resource", params: { "query_foo" => "bar" })
|
43
|
+
response.class.name
|
44
|
+
# => OAuth2::Response
|
45
|
+
```
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
1. Fork it
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
51
|
+
3. Commit your changes (`git commit -am "Add some feature"`)
|
52
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
53
|
+
5. Create new Pull Request
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module OAuth2
|
2
|
+
class Connection
|
3
|
+
class ConnectionError < StandardError; end
|
4
|
+
|
5
|
+
# Public: Initializes a new Faraday::Connection.
|
6
|
+
#
|
7
|
+
# url - NSURL or String base URL to use as a prefix for all
|
8
|
+
# requests (optional).
|
9
|
+
# options - Hash
|
10
|
+
# :url - NSURL or String base URL (default: "http:/").
|
11
|
+
# :params - Hash of URI query unencoded key/value pairs.
|
12
|
+
# :headers - Hash of unencoded HTTP header key/value pairs.
|
13
|
+
# :request - Hash of request options.
|
14
|
+
# :ssl - Hash of SSL options.
|
15
|
+
def initialize(url = nil, options = {})
|
16
|
+
if url.is_a?(Hash)
|
17
|
+
options = url.with_indifferent_access
|
18
|
+
url = options[:url]
|
19
|
+
else
|
20
|
+
options = options.with_indifferent_access
|
21
|
+
end
|
22
|
+
|
23
|
+
@headers = {}.with_indifferent_access
|
24
|
+
@options = {}.with_indifferent_access
|
25
|
+
@params = {}.with_indifferent_access
|
26
|
+
@ssl = {}.with_indifferent_access
|
27
|
+
|
28
|
+
url = NSURL.URLWithString(url) if url.is_a?(String)
|
29
|
+
self.url_prefix = url || NSURL.URLWithString("http:/")
|
30
|
+
|
31
|
+
@headers.update(options[:headers]) if options[:headers]
|
32
|
+
@options.update(options[:request]) if options[:request]
|
33
|
+
@params.update(options[:params]) if options[:params]
|
34
|
+
@ssl.update(options[:ssl]) if options[:ssl]
|
35
|
+
|
36
|
+
@headers[:user_agent] ||= "Motion-OAuth2 v#{Version}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Takes a relative url for a request and combines it with the defaults
|
40
|
+
# set on the connection instance.
|
41
|
+
#
|
42
|
+
# conn = OAuth2::Connection.new { ... }
|
43
|
+
# conn.url_prefix = "https://sushi.com/api?token=abc"
|
44
|
+
# conn.scheme # => https
|
45
|
+
# conn.path_prefix # => "/api"
|
46
|
+
#
|
47
|
+
# conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
|
48
|
+
# conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
|
49
|
+
#
|
50
|
+
def build_url(url = nil, extra_params = nil)
|
51
|
+
url = url.absoluteString if url.is_a?(NSURL)
|
52
|
+
nsurl = NSURL.URLWithString(url, relativeToURL: self.url_prefix)
|
53
|
+
base_url = nsurl.absoluteString.gsub("?#{nsurl.query}", "")
|
54
|
+
|
55
|
+
url_params = Utils.params_from_query(nsurl.query)
|
56
|
+
query_values = params.dup.merge(url_params)
|
57
|
+
query_values.update(extra_params) if extra_params
|
58
|
+
|
59
|
+
if query_values.length > 0
|
60
|
+
query_string = Utils.query_from_params(query_values)
|
61
|
+
"#{base_url}?#{query_string}"
|
62
|
+
else
|
63
|
+
base_url
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# The host of the URL
|
68
|
+
# @return [String]
|
69
|
+
def host
|
70
|
+
self.url_prefix.host
|
71
|
+
end
|
72
|
+
|
73
|
+
# Builds and runs the NSURLRequest.
|
74
|
+
#
|
75
|
+
# method - The Symbol HTTP method.
|
76
|
+
# url - The String or NSURL to access.
|
77
|
+
# body - The String body
|
78
|
+
# headers - Hash of unencoded HTTP header key/value pairs.
|
79
|
+
# parse - Response parse options @see Response::initialize
|
80
|
+
#
|
81
|
+
# Returns a OAuth2::Response.
|
82
|
+
def run_request(method, url, body, headers, parse)
|
83
|
+
unless METHODS.include?(method)
|
84
|
+
fail ArgumentError, "unknown http method: #{method}"
|
85
|
+
end
|
86
|
+
|
87
|
+
url = NSURL.URLWithString(url) if url.is_a?(String)
|
88
|
+
request = NSMutableURLRequest.requestWithURL(url)
|
89
|
+
request.setHTTPMethod(method)
|
90
|
+
|
91
|
+
if body
|
92
|
+
body = Utils.query_from_params(body) if body.is_a?(Hash)
|
93
|
+
request.setHTTPBody(body)
|
94
|
+
end
|
95
|
+
|
96
|
+
if headers
|
97
|
+
headers.each do |key, value|
|
98
|
+
request.addValue(value, forHTTPHeaderField: key)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
response = Pointer.new(:object)
|
103
|
+
error = Pointer.new(:object)
|
104
|
+
data = NSURLConnection.sendSynchronousRequest(request, returningResponse: response, error: error)
|
105
|
+
|
106
|
+
if error[0]
|
107
|
+
if error[0].code == NSURLErrorUserCancelledAuthentication
|
108
|
+
return Response.new({
|
109
|
+
data: nil,
|
110
|
+
error: error[0],
|
111
|
+
headers: {},
|
112
|
+
response: nil,
|
113
|
+
status: 401
|
114
|
+
}, {
|
115
|
+
parse: parse
|
116
|
+
})
|
117
|
+
else
|
118
|
+
fail ConnectionError, error[0].localizedDescription
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
Response.new({
|
123
|
+
data: data,
|
124
|
+
headers: response[0].allHeaderFields,
|
125
|
+
response: response[0],
|
126
|
+
status: response[0].statusCode
|
127
|
+
}, {
|
128
|
+
parse: parse
|
129
|
+
})
|
130
|
+
end
|
131
|
+
|
132
|
+
# Public: Parses the giving url with URI and stores the individual
|
133
|
+
# components in this connection. These components serve as defaults for
|
134
|
+
# requests made by this connection.
|
135
|
+
#
|
136
|
+
# url - A String or NSURL.
|
137
|
+
#
|
138
|
+
# Examples
|
139
|
+
#
|
140
|
+
# conn = Faraday::Connection.new { ... }
|
141
|
+
# conn.url_prefix = "https://sushi.com/api"
|
142
|
+
# conn.scheme # => https
|
143
|
+
# conn.path_prefix # => "/api"
|
144
|
+
#
|
145
|
+
# conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
|
146
|
+
#
|
147
|
+
# Returns the parsed URI from teh given input..
|
148
|
+
def url_prefix=(url)
|
149
|
+
url = NSURL.URLWithString(url) if url.is_a?(String)
|
150
|
+
@url_prefix = url
|
151
|
+
|
152
|
+
url_params = Utils.params_from_query(@url_prefix.query)
|
153
|
+
self.params = self.params.merge(url_params)
|
154
|
+
|
155
|
+
@url_prefix = NSURL.URLWithString(@url_prefix.absoluteString.gsub("?#{@url_prefix.query}", ""))
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module OAuth2
|
2
|
+
class MACToken < AccessToken
|
3
|
+
# Set the HMAC algorithm
|
4
|
+
#
|
5
|
+
# @param [String] alg the algorithm to use (one of 'hmac-sha-1', 'hmac-sha-256')
|
6
|
+
def algorithm=(alg)
|
7
|
+
@algorithm = begin
|
8
|
+
case alg.to_s
|
9
|
+
when "hmac-sha-1"
|
10
|
+
"hmacSha1:hmacKey"
|
11
|
+
when "hmac-sha-256"
|
12
|
+
"hmacSha256:hmacKey"
|
13
|
+
else
|
14
|
+
fail(ArgumentError, "Unsupported algorithm")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate the Base64-encoded HMAC digest signature
|
20
|
+
#
|
21
|
+
# @param [Fixnum] timestamp the timestamp of the request in seconds since epoch
|
22
|
+
# @param [String] nonce the MAC header nonce
|
23
|
+
# @param [Symbol] verb the HTTP request method
|
24
|
+
# @param [String] url the HTTP URL path of the request
|
25
|
+
def signature(timestamp, nonce, verb, url)
|
26
|
+
nsurl = NSURL.URLWithString(url.to_s)
|
27
|
+
fail(ArgumentError, "could not parse \"#{url}\" into NSURL") unless nsurl.host
|
28
|
+
|
29
|
+
path = nsurl.path
|
30
|
+
path = "/" if path == ""
|
31
|
+
|
32
|
+
port = nsurl.port
|
33
|
+
port = nsurl.scheme == "https" ? 443 : 80 unless port
|
34
|
+
|
35
|
+
signature = [
|
36
|
+
timestamp,
|
37
|
+
nonce,
|
38
|
+
verb.to_s.upcase,
|
39
|
+
path,
|
40
|
+
nsurl.host,
|
41
|
+
port,
|
42
|
+
"", nil
|
43
|
+
].join("\n")
|
44
|
+
|
45
|
+
digest = CocoaSecurity.send(algorithm, signature, secret)
|
46
|
+
digest.base64
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def generate_nonce
|
52
|
+
timestamp = Time.now.utc.to_i
|
53
|
+
uuid = CFUUIDCreate(nil)
|
54
|
+
string = CFUUIDCreateString(nil, uuid)
|
55
|
+
CocoaSecurity.md5([timestamp, string].join(":")).hex
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module OAuth2
|
2
|
+
class Response
|
3
|
+
attr_reader :body
|
4
|
+
attr_accessor :data, :headers, :response, :status
|
5
|
+
|
6
|
+
# Initializes a Response instance
|
7
|
+
#
|
8
|
+
# @param params [Hash]
|
9
|
+
# @option params [NSData] :data
|
10
|
+
# @option params [Hash] :headers
|
11
|
+
# @option params [Integer] :status
|
12
|
+
# @param data [NSData]
|
13
|
+
# @param opts [Hash] options in which to initialize the instance
|
14
|
+
# @option opts [Symbol] :parse (:automatic) how to parse the response body. one of :query (for x-www-form-urlencoded),
|
15
|
+
# :json, or :automatic (determined by Content-Type response header)
|
16
|
+
def initialize(params = {}, opts = {})
|
17
|
+
params.each do |key, value|
|
18
|
+
self.send("#{key}=", value)
|
19
|
+
end
|
20
|
+
@options = { parse: :automatic }.merge(opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
# The HTTP response body
|
24
|
+
def body
|
25
|
+
return "" unless data.is_a?(NSData)
|
26
|
+
NSString.alloc.initWithData(data, encoding: NSUTF8StringEncoding)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Attempts to determine the content type of the response.
|
30
|
+
def content_type
|
31
|
+
((self.headers.values_at("content-type", "Content-Type").compact.first || "").split(";").first || "").strip
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module OAuth2
|
2
|
+
module Strategy
|
3
|
+
# The Client Assertion Strategy
|
4
|
+
#
|
5
|
+
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
|
6
|
+
#
|
7
|
+
# Sample usage:
|
8
|
+
# client = OAuth2::Client.new(client_id, client_secret,
|
9
|
+
# :site => 'http://localhost:8080')
|
10
|
+
#
|
11
|
+
# params = {:hmac_secret => "some secret",
|
12
|
+
# # or :private_key => "private key string",
|
13
|
+
# :iss => "http://localhost:3001",
|
14
|
+
# :prn => "me@here.com",
|
15
|
+
# :exp => Time.now.utc.to_i + 3600}
|
16
|
+
#
|
17
|
+
# access = client.assertion.get_token(params)
|
18
|
+
# access.token # actual access_token string
|
19
|
+
# access.get("/api/stuff") # making api calls with access token in header
|
20
|
+
#
|
21
|
+
class Assertion < Base
|
22
|
+
def build_assertion(params)
|
23
|
+
claims = {
|
24
|
+
iss: params[:iss],
|
25
|
+
aud: params[:aud],
|
26
|
+
prn: params[:prn],
|
27
|
+
exp: params[:exp]
|
28
|
+
}
|
29
|
+
if params[:hmac_secret]
|
30
|
+
CocoaSecurity.hmacSha256(claims.to_s, hmacKey: params[:hmac_secret]).hex
|
31
|
+
elsif params[:private_key]
|
32
|
+
CocoaSecurity.hmacSha256(claims.to_s, hmacKey: params[:private_key]).hex
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module OAuth2
|
2
|
+
module Strategy
|
3
|
+
# The Client Credentials Strategy
|
4
|
+
#
|
5
|
+
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
|
6
|
+
class ClientCredentials < Base
|
7
|
+
# Returns the Authorization header value for Basic Authentication
|
8
|
+
#
|
9
|
+
# @param [String] The client ID
|
10
|
+
# @param [String] the client secret
|
11
|
+
def authorization(client_id, client_secret)
|
12
|
+
encoder = CocoaSecurityEncoder.new
|
13
|
+
authorization = encoder.base64("#{client_id}:#{client_secret}".to_data).gsub("\n", "")
|
14
|
+
"Basic #{authorization}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module OAuth2
|
2
|
+
class Utils
|
3
|
+
class ParserError < StandardError; end
|
4
|
+
|
5
|
+
# Serialize a JSON object
|
6
|
+
# @return [String]
|
7
|
+
def self.serialize_json(obj)
|
8
|
+
NSJSONSerialization.dataWithJSONObject(obj, options: 0, error: nil).to_str
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns a Hash from a URL query string
|
12
|
+
# @param query [String]
|
13
|
+
# @return [Hash]
|
14
|
+
def self.params_from_query(query)
|
15
|
+
query ||= ""
|
16
|
+
key_values = query.split("&")
|
17
|
+
hash = {}
|
18
|
+
|
19
|
+
key_values.each do |key_value|
|
20
|
+
key_value = key_value.split("=")
|
21
|
+
hash[key_value[0].to_sym] = key_value[1]
|
22
|
+
end
|
23
|
+
|
24
|
+
hash.with_indifferent_access
|
25
|
+
end
|
26
|
+
|
27
|
+
# Parses a string or data object and converts it in data structure.
|
28
|
+
#
|
29
|
+
# @param [String, NSData] str_data the string or data to serialize.
|
30
|
+
# @raise [ParserError] If the parsing of the passed string/data isn't valid.
|
31
|
+
# @return [Hash, Array, NilClass] the converted data structure, nil if the incoming string isn't valid.
|
32
|
+
def self.parse_json(str_data, &block)
|
33
|
+
return nil unless str_data
|
34
|
+
data = str_data.respond_to?("dataUsingEncoding:") ? str_data.dataUsingEncoding(NSUTF8StringEncoding) : str_data
|
35
|
+
opts = NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments
|
36
|
+
error = Pointer.new(:id)
|
37
|
+
obj = NSJSONSerialization.JSONObjectWithData(data, options: opts, error: error)
|
38
|
+
fail ParserError, error[0].description if error[0]
|
39
|
+
if block_given?
|
40
|
+
yield obj
|
41
|
+
else
|
42
|
+
obj
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns a URL query string from a params Hash
|
47
|
+
# @param params [Hash]
|
48
|
+
# @return [String]
|
49
|
+
def self.query_from_params(params)
|
50
|
+
key_values = []
|
51
|
+
params.each do |key, value|
|
52
|
+
value = value.to_s.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
|
53
|
+
key_values << "#{key}=#{value}"
|
54
|
+
end
|
55
|
+
|
56
|
+
if key_values.length > 0
|
57
|
+
"#{key_values.join('&')}"
|
58
|
+
else
|
59
|
+
""
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/oauth2.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "motion-cocoapods"
|
3
|
+
require "motion-support/core_ext/hash"
|
4
|
+
|
5
|
+
unless defined?(Motion::Project::Config)
|
6
|
+
fail "This file must be required within a RubyMotion project Rakefile."
|
7
|
+
end
|
8
|
+
|
9
|
+
lib_dir_path = File.dirname(File.expand_path(__FILE__))
|
10
|
+
Motion::Project::App.setup do |app|
|
11
|
+
# Load shared files
|
12
|
+
app.files.unshift(Dir.glob(File.join(lib_dir_path, "oauth2/**/*.rb")))
|
13
|
+
|
14
|
+
# Load files shared by Cocoa platforms
|
15
|
+
case app.template
|
16
|
+
when :ios, :osx
|
17
|
+
app.files.unshift(Dir.glob(File.join(lib_dir_path, "oauth2-cocoa/**/*.rb")))
|
18
|
+
end
|
19
|
+
|
20
|
+
app.pods do
|
21
|
+
pod "CocoaSecurity", "~> 1.2"
|
22
|
+
end
|
23
|
+
end
|