oauth2_rails 0.1.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: faf99318fa6e905188159c8c8acc3f342dcb45af
4
+ data.tar.gz: 324ee6e1be1144b814cc16762f77de8e5f2dc57d
5
+ SHA512:
6
+ metadata.gz: db2340bbe42c9f302c172b959ce73f05f730bdc940b875a58394190cd2fbd716c4898fe8d600d602e4c643baef3911f6350864f01a1569d7eb0aaeefdf024bb7
7
+ data.tar.gz: f242b72e1e1727a4d57c57e528eabe9b972dca703540565ec2e72bb15a2d1afade4bdd544a0a11bab5390245366ad6645b482b0a6808002ba9248914785767c8
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .idea/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in oauth2_rails.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Oauth2Rails
2
+
3
+ A gem that currently provides support for the Fitbit API using Oauth2 protocol.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'oauth2_rails'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install oauth2_rails
20
+
21
+ ## Contributing
22
+ One project I would be very interested in is extracting this to a more general framework, that works with most clients on rails. For example, I'm sure the Fitbit flow is a little different, so it would not work always with other providers.
23
+
24
+ 1. Fork it ( https://github.com/[my-github-username]/oauth2_rails/fork )
25
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
26
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
27
+ 4. Push to the branch (`git push origin my-new-feature`)
28
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "oauth2_rails"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,16 @@
1
+ require 'oauth2_rails/version'
2
+ require 'oauth2_rails/base'
3
+ require 'oauth2_rails/auth'
4
+ require 'oauth2_rails/errors'
5
+ require 'oauth2_rails/fitbit'
6
+ require 'oauth2_rails/response'
7
+ require 'oauth2_rails/user'
8
+ require 'faraday'
9
+ require 'base64'
10
+
11
+ module Oauth2Rails
12
+ extend self
13
+ def new(user, options = {})
14
+ Client.new(user, options = {})
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ require 'oauth2_rails/base'
2
+
3
+ module Oauth2Rails
4
+ class Auth < Base
5
+
6
+ def initialize(options = {})
7
+ @state = options[:state]
8
+ super(options)
9
+ end
10
+
11
+ # SAMPLE AUTHORIZATION REQUEST
12
+ # GET: https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=22942C&
13
+ # redirect_uri=http%3A%2F%2Fexample.com%2Fcallback&
14
+ # scope=activity%20nutrition%20heartrate
15
+ def authorize_url
16
+ body = { response_type: 'code', client_id: @oauth_id, redirect_uri: @redirect_uri, scope: @scope, state: @state }
17
+ connection(@authorize_site).build_url(@authorize_path, body).to_s
18
+ end
19
+
20
+ # SAMPLE POST REQUEST
21
+ # POST: https://api.fitbit.com/oauth2/token
22
+ # BODY: client_id=22942C&grant_type=authorization_code&redirect_uri=http%3A%2F%2Fexample.com%2Fcallback&code=1234567890
23
+ # HEADERS: Authorization: Basic Y2xpZW50X2lkOmNsaWVudCBzZWNyZXQ=
24
+ # Content-Type: application/x-www-form-urlencoded
25
+ def get_token(code)
26
+ body = { grant_type: 'authorization_code', client_id: @oauth_id, redirect_uri: @redirect_uri, code: code }
27
+ tokens = call(:post, @token_path, body: body)
28
+ User.new(tokens.json_body)
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ require 'oauth2_rails/errors'
2
+
3
+ module Oauth2Rails
4
+ class Base
5
+
6
+ def initialize(options = {})
7
+ @oauth_id = options[:oauth_id] || OAUTH2_RAILS_ID
8
+ @oauth_secret = options[:oauth_secret] || OAUTH2_RAILS_SECRET
9
+ @redirect_uri = options[:redirect_uri] || 'http://localhost:3000/oauth2_callbacks/fitbit'
10
+ @authorize_site = options[:authorize_site] || 'https://www.fitbit.com'
11
+ @authorize_path = options[:authorize_path] || '/oauth2/authorize'
12
+ @api_site = options[:api_site] || 'https://api.fitbit.com'
13
+ @token_path = options[:token_path] || '/oauth2/token'
14
+ @scope = options[:scope] || 'heartrate'
15
+ end
16
+
17
+ def connection(url)
18
+ Faraday.new(url: url) do |faraday|
19
+ faraday.request :url_encoded
20
+ faraday.response :logger
21
+ faraday.adapter Faraday.default_adapter
22
+ end
23
+ end
24
+
25
+ def call(action, destination, options = {})
26
+ user = options[:user]
27
+ site = options[:site] || @api_site
28
+
29
+ if user
30
+ auth_header = "Bearer #{user}"
31
+ else
32
+ encoded = Base64.strict_encode64("#{@oauth_id}:#{@oauth_secret}")
33
+ auth_header = "Basic #{encoded}"
34
+ end
35
+
36
+ call = connection(site).send(action) do |req|
37
+ req.url destination
38
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
39
+ req.headers['Authorization'] = auth_header
40
+ req.body = options[:body]
41
+ end
42
+
43
+ response = Response.new(call)
44
+ case response.status
45
+ when 400 ; raise Oauth2Rails::Errors::BadRequest, "400 #{response.error_message}"
46
+ when 404 ; raise Oauth2Rails::Errors::NotFound, "404 #{response.error_message}"
47
+ when 409 ; raise Oauth2Rails::Errors::Conflict, "409 #{response.error_message}"
48
+ when 500 ; raise Oauth2Rails::Errors::InternalServer, "500 #{response.error_message}"
49
+ when 502 ; raise Oauth2Rails::Errors::BadGateway, "502 #{response.error_message}"
50
+ when 401 ; raise Oauth2Rails::Errors::Unauthorized, "401 #{response.error_message}"
51
+ else ; response
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,30 @@
1
+ require 'oauth2_rails/base'
2
+ require 'oauth2_rails/user'
3
+
4
+ module Oauth2Rails
5
+ class Client < Base
6
+
7
+ def initialize(user, options = {})
8
+ super(options)
9
+ @user = user
10
+ end
11
+
12
+ def api_call(destination)
13
+ begin
14
+ call(:get, destination, user: @user.access_token)
15
+ rescue Oauth2Rails::Errors::Unauthorized
16
+ refresh
17
+ call(:get, destination, user: @user.access_token)
18
+ end
19
+ end
20
+
21
+ def refresh
22
+ response = call(:post, "#{@token_path}?grant_type=refresh_token&refresh_token=#{@user.refresh_token}")
23
+ @user.update!(
24
+ access_token: response.access_token, refresh_token: response.refresh_token,
25
+ expiry: Time.now + response.expires_every
26
+ )
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ module Oauth2Rails
2
+ module Errors
3
+ # 400 Bad Request | Any case where either endpoint doesn't exist,
4
+ # | resource path parameters are invalid, POST request
5
+ # | parameters are invalid or no Authentication header provided.
6
+ # | This doesn't include invalid specific resource ids
7
+ # 401 Unauthorized | The OAuth Authorization header provided and is invalid
8
+ # | (consider looking in response body). Client or authorized
9
+ # | user have no privilege to view requested data (for example,
10
+ # | requested resource's owner has privacy permission "You" or "Friends"
11
+ # | for requested resource)
12
+ # 404 Not Found | The resource with given id doesn't exist
13
+ # 409 Conflict | Either you hit the rate limiting quota for the client or
14
+ # | for the viewer, or you trying to create conflicting resources (consider looking at errorType)
15
+ # 500 Internal Error | Something is terribly wrong on our side (and we are working on it). Try your request later
16
+ # 502 Bad Gateway | We will be back soon. Maintenance!
17
+
18
+ class BadRequest < StandardError
19
+ end
20
+
21
+ class Unauthorized < StandardError
22
+ end
23
+
24
+ class NotFound < StandardError
25
+ end
26
+
27
+ class Conflict < StandardError
28
+ end
29
+
30
+ class InternalServer < StandardError
31
+ end
32
+
33
+ class BadGateway < StandardError
34
+ end
35
+
36
+ class InvalidArgument < StandardError
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,65 @@
1
+ require 'oauth2_rails/base'
2
+ require 'oauth2_rails/client'
3
+ require 'oauth2_rails/user'
4
+
5
+ module Oauth2Rails
6
+ class Fitbit < Client
7
+ ## => PROFILE
8
+ # https://api.fitbit.com/1/user/-/profile.json
9
+ def profile
10
+ Profile.new(api_call('/1/user/-/profile.json').json_body)
11
+ end
12
+
13
+ def raw_profile
14
+ api_call('/1/user/-/profile.json')
15
+ end
16
+
17
+ ## => HEART DATA
18
+ # https://api.fitbit.com/1/user/-/activities/heart/date/today/1d.json
19
+ def daily_heart(start_date)
20
+ api_call("/1/user/-/activities/heart/date/#{start_date}/1d.json")
21
+ end
22
+
23
+ # https://api.fitbit.com/1/user/-/activities/heart/date/2015-05-07/1d/1sec/time/12:20/12:45.json
24
+ def minute_heart(days, seconds, start_date, start_time, end_time)
25
+ api_call("/1/user/-/activities/heart/date/#{start_date}/#{days}d/#{seconds}sec/time/#{start_time}/#{end_time}.json")
26
+ end
27
+
28
+ ## => SLEEP DATA
29
+ # Simple get sleep
30
+ def sleep(date)
31
+ api_call("/1/user/-/sleep/date/#{date}.json")
32
+ end
33
+
34
+ # Sleep time series
35
+ # /1/user/-/sleep/minutesAsleep/date/today/2010-08-27.json
36
+ # sleep/startTime ; sleep/timeInBed ; sleep/minutesAsleep
37
+ # sleep/awakeningsCount ; sleep/minutesAwake ; sleep/minutesToFallAsleep
38
+ # sleep/minutesAfterWakeup ; sleep/efficiency
39
+ def time_asleep(start_date, end_date)
40
+ api_call("/1/user/-/sleep/minutesAsleep/date/#{start_date}/#{end_date}.json")
41
+ end
42
+
43
+ def sleep_start(start_date, end_date)
44
+ api_call("/1/user/-/sleep/startTime/date/#{start_date}/#{end_date}.json")
45
+ end
46
+
47
+ def sleep_efficiency(start_date, end_date)
48
+ api_call("/1/user/-/sleep/efficiency/date/#{start_date}/#{end_date}.json")
49
+ end
50
+
51
+ def sleep_total_time(start_date, end_date)
52
+ api_call("/1/user/-/sleep/minutesAsleep/date/#{start_date}/#{end_date}.json")
53
+ end
54
+
55
+ ## => BODY INFORMATION
56
+ def body_weight(date)
57
+ api_call("/1/user/-/body/log/weight/date/#{date}.json")
58
+ end
59
+
60
+ ## => ACTIVITIES
61
+ def recent_activites
62
+ api_call("/1/user/-/activities/recent.json")
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ require 'json'
2
+
3
+ module Oauth2Rails
4
+ class Response
5
+
6
+ def initialize(response)
7
+ @far_resp = response
8
+ end
9
+
10
+ def status
11
+ @far_resp.status
12
+ end
13
+
14
+ def headers
15
+ @far_resp.headers
16
+ end
17
+
18
+ def body
19
+ @far_resp.body
20
+ end
21
+
22
+ def json_body
23
+ JSON.parse @far_resp.body
24
+ end
25
+
26
+ def errors
27
+ json_body['errors'][0] if status != 200
28
+ end
29
+
30
+ def error_message
31
+ errors['message'] if errors
32
+ end
33
+
34
+ def refresh_token
35
+ json_body['access_token']
36
+ end
37
+
38
+ def access_token
39
+ json_body['access_token']
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,65 @@
1
+ module Oauth2Rails
2
+ class Profile
3
+ def initialize(profile)
4
+ @profile = profile
5
+ end
6
+
7
+ def json_response
8
+ @profile
9
+ end
10
+
11
+ def id
12
+ @profile['user']['encodedId']
13
+ end
14
+
15
+ def full_name
16
+ @profile['user']['fullName']
17
+ end
18
+
19
+ def display_name
20
+ @profile['user']['displayName']
21
+ end
22
+
23
+ def country
24
+ @profile['user']['country']
25
+ end
26
+
27
+ def state
28
+ @profile['user']['state']
29
+ end
30
+
31
+ def city
32
+ @profile['user']['city']
33
+ end
34
+
35
+ def about_me
36
+ @profile['user']['aboutMe']
37
+ end
38
+ end
39
+
40
+ class User
41
+ def initialize(auth)
42
+ @token = auth
43
+ end
44
+
45
+ def json_response
46
+ @token
47
+ end
48
+
49
+ def id
50
+ @token['user_id']
51
+ end
52
+
53
+ def access_token
54
+ @token['access_token']
55
+ end
56
+
57
+ def refresh_token
58
+ @token['refresh_token']
59
+ end
60
+
61
+ def expires_every
62
+ @token['expires_in']
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module Oauth2Rails
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'oauth2_rails/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "oauth2_rails"
8
+ spec.version = Oauth2Rails::VERSION
9
+ spec.authors = ["Colin Walker"]
10
+ spec.email = ["cjwalker@sfu.ca"]
11
+
12
+ spec.summary = %q{Oauth2 Rails based client for Fitbit specifically.}
13
+ spec.homepage = "https://github.com/ColDog/oauth2-fitbit-rails"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "faraday", ">= 0.7.0"
21
+ spec.add_development_dependency "bundler", "~> 1.9"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oauth2_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Colin Walker
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-07-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.7.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.7.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description:
56
+ email:
57
+ - cjwalker@sfu.ca
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - README.md
67
+ - Rakefile
68
+ - bin/console
69
+ - bin/setup
70
+ - lib/oauth2_rails.rb
71
+ - lib/oauth2_rails/auth.rb
72
+ - lib/oauth2_rails/base.rb
73
+ - lib/oauth2_rails/client.rb
74
+ - lib/oauth2_rails/errors.rb
75
+ - lib/oauth2_rails/fitbit.rb
76
+ - lib/oauth2_rails/response.rb
77
+ - lib/oauth2_rails/user.rb
78
+ - lib/oauth2_rails/version.rb
79
+ - oauth2_rails.gemspec
80
+ homepage: https://github.com/ColDog/oauth2-fitbit-rails
81
+ licenses: []
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 2.4.6
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Oauth2 Rails based client for Fitbit specifically.
103
+ test_files: []