motionauth-oauth2 1.0.0

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