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 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
+ [![Build Status](https://travis-ci.org/motionauth/oauth2.svg)](https://travis-ci.org/motionauth/oauth2)
4
+ [![Code Climate](https://codeclimate.com/github/motionauth/oauth2/badges/gpa.svg)](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