openc-asana 0.1.2

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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +4 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +11 -0
  6. data/.travis.yml +12 -0
  7. data/.yardopts +5 -0
  8. data/CODE_OF_CONDUCT.md +13 -0
  9. data/Gemfile +21 -0
  10. data/Guardfile +86 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +355 -0
  13. data/Rakefile +65 -0
  14. data/examples/Gemfile +6 -0
  15. data/examples/Gemfile.lock +59 -0
  16. data/examples/api_token.rb +21 -0
  17. data/examples/cli_app.rb +25 -0
  18. data/examples/events.rb +38 -0
  19. data/examples/omniauth_integration.rb +54 -0
  20. data/lib/asana.rb +12 -0
  21. data/lib/asana/authentication.rb +8 -0
  22. data/lib/asana/authentication/oauth2.rb +42 -0
  23. data/lib/asana/authentication/oauth2/access_token_authentication.rb +51 -0
  24. data/lib/asana/authentication/oauth2/bearer_token_authentication.rb +32 -0
  25. data/lib/asana/authentication/oauth2/client.rb +50 -0
  26. data/lib/asana/authentication/token_authentication.rb +20 -0
  27. data/lib/asana/client.rb +124 -0
  28. data/lib/asana/client/configuration.rb +165 -0
  29. data/lib/asana/errors.rb +92 -0
  30. data/lib/asana/http_client.rb +155 -0
  31. data/lib/asana/http_client/environment_info.rb +53 -0
  32. data/lib/asana/http_client/error_handling.rb +103 -0
  33. data/lib/asana/http_client/response.rb +32 -0
  34. data/lib/asana/resource_includes/attachment_uploading.rb +33 -0
  35. data/lib/asana/resource_includes/collection.rb +68 -0
  36. data/lib/asana/resource_includes/event.rb +51 -0
  37. data/lib/asana/resource_includes/event_subscription.rb +14 -0
  38. data/lib/asana/resource_includes/events.rb +103 -0
  39. data/lib/asana/resource_includes/registry.rb +63 -0
  40. data/lib/asana/resource_includes/resource.rb +103 -0
  41. data/lib/asana/resource_includes/response_helper.rb +14 -0
  42. data/lib/asana/resources.rb +14 -0
  43. data/lib/asana/resources/attachment.rb +44 -0
  44. data/lib/asana/resources/project.rb +154 -0
  45. data/lib/asana/resources/story.rb +64 -0
  46. data/lib/asana/resources/tag.rb +120 -0
  47. data/lib/asana/resources/task.rb +300 -0
  48. data/lib/asana/resources/team.rb +55 -0
  49. data/lib/asana/resources/user.rb +72 -0
  50. data/lib/asana/resources/workspace.rb +91 -0
  51. data/lib/asana/ruby2_0_0_compatibility.rb +3 -0
  52. data/lib/asana/version.rb +5 -0
  53. data/lib/templates/index.js +8 -0
  54. data/lib/templates/resource.ejs +225 -0
  55. data/openc-asana.gemspec +32 -0
  56. data/package.json +7 -0
  57. metadata +200 -0
data/Rakefile ADDED
@@ -0,0 +1,65 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'rubocop/rake_task'
3
+ require 'yard'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ RuboCop::RakeTask.new
8
+
9
+ YARD::Rake::YardocTask.new do |t|
10
+ t.stats_options = ['--list-undoc']
11
+ end
12
+
13
+ desc 'Generates a test resource from a YAML using the resource template.'
14
+ task :codegen do
15
+ `node spec/support/codegen.js`
16
+ end
17
+
18
+ namespace :bump do
19
+ def read_version
20
+ File.readlines('./lib/asana/version.rb')
21
+ .detect { |l| l =~ /VERSION/ }
22
+ .scan(/VERSION = '([^']+)/).flatten.first.split('.')
23
+ .map { |n| Integer(n) }
24
+ end
25
+
26
+ # rubocop:disable Metrics/MethodLength
27
+ def write_version(major, minor, patch)
28
+ str = <<-EOS
29
+ #:nodoc:
30
+ module Asana
31
+ # Public: Version of the gem.
32
+ VERSION = '#{major}.#{minor}.#{patch}'
33
+ end
34
+ EOS
35
+ File.open('./lib/asana/version.rb', 'w') do |f|
36
+ f.write str
37
+ end
38
+
39
+ new_version = "#{major}.#{minor}.#{patch}"
40
+ system('git add lib/asana/version.rb')
41
+ system(%(git commit -m "Bumped to #{new_version}" && ) +
42
+ %(git tag -a v#{new_version} -m "Version #{new_version}"))
43
+ puts "\nRun git push --tags to release."
44
+ end
45
+
46
+ desc 'Bumps a patch version'
47
+ task :patch do
48
+ major, minor, patch = read_version
49
+ write_version major, minor, patch + 1
50
+ end
51
+
52
+ desc 'Bumps a minor version'
53
+ task :minor do
54
+ major, minor, = read_version
55
+ write_version major, minor + 1, 0
56
+ end
57
+
58
+ desc 'Bumps a major version'
59
+ task :major do
60
+ major, = read_version
61
+ write_version major + 1, 0, 0
62
+ end
63
+ end
64
+
65
+ task default: [:codegen, :spec, :rubocop, :yard]
data/examples/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'omniauth'
4
+ gem 'omniauth-asana'
5
+ gem 'sinatra'
6
+ gem 'asana', path: '../'
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ asana (0.1.1)
5
+ faraday (~> 0.9)
6
+ faraday_middleware (~> 0.9)
7
+ faraday_middleware-multi_json (~> 0.0)
8
+ oauth2 (~> 1.0)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ faraday (0.9.1)
14
+ multipart-post (>= 1.2, < 3)
15
+ faraday_middleware (0.9.1)
16
+ faraday (>= 0.7.4, < 0.10)
17
+ faraday_middleware-multi_json (0.0.6)
18
+ faraday_middleware
19
+ multi_json
20
+ hashie (3.4.1)
21
+ jwt (1.5.0)
22
+ multi_json (1.11.0)
23
+ multi_xml (0.5.5)
24
+ multipart-post (2.0.0)
25
+ oauth2 (1.0.0)
26
+ faraday (>= 0.8, < 0.10)
27
+ jwt (~> 1.0)
28
+ multi_json (~> 1.3)
29
+ multi_xml (~> 0.5)
30
+ rack (~> 1.2)
31
+ omniauth (1.2.2)
32
+ hashie (>= 1.2, < 4)
33
+ rack (~> 1.0)
34
+ omniauth-asana (0.0.1)
35
+ omniauth (~> 1.0)
36
+ omniauth-oauth2 (~> 1.1)
37
+ omniauth-oauth2 (1.3.0)
38
+ oauth2 (~> 1.0)
39
+ omniauth (~> 1.2)
40
+ rack (1.6.1)
41
+ rack-protection (1.5.3)
42
+ rack
43
+ sinatra (1.4.6)
44
+ rack (~> 1.4)
45
+ rack-protection (~> 1.4)
46
+ tilt (>= 1.3, < 3)
47
+ tilt (2.0.1)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ asana!
54
+ omniauth
55
+ omniauth-asana
56
+ sinatra
57
+
58
+ BUNDLED WITH
59
+ 1.10.3
@@ -0,0 +1,21 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ require 'asana'
4
+
5
+ api_token = ENV['ASANA_API_TOKEN']
6
+ unless api_token
7
+ abort "Run this program with the env var ASANA_API_TOKEN.\n" \
8
+ "Go to http://app.asana.com/-/account_api to see your API token."
9
+ end
10
+
11
+ client = Asana::Client.new do |c|
12
+ c.authentication :api_token, api_token
13
+ end
14
+
15
+ puts "My Workspaces:"
16
+ client.workspaces.find_all.each do |workspace|
17
+ puts "\t* #{workspace.name} - tags:"
18
+ client.tags.find_by_workspace(workspace: workspace.id).each do |tag|
19
+ puts "\t\t- #{tag.name}"
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ require 'asana'
4
+
5
+ id, secret = ENV['ASANA_CLIENT_ID'], ENV['ASANA_CLIENT_SECRET']
6
+ unless id && secret
7
+ abort "Run this program with the env vars ASANA_CLIENT_ID and ASANA_CLIENT_SECRET.\n" \
8
+ "Refer to https://asana.com/developers/documentation/getting-started/authentication "\
9
+ "to get your credentials." \
10
+ "The redirect URI for your application should be \"urn:ietf:wg:oauth:2.0:oob\"."
11
+ end
12
+
13
+ access_token = Asana::Authentication::OAuth2.offline_flow(client_id: id,
14
+ client_secret: secret)
15
+ client = Asana::Client.new do |c|
16
+ c.authentication :oauth2, access_token
17
+ end
18
+
19
+ puts "My Workspaces:"
20
+ client.workspaces.find_all.each do |workspace|
21
+ puts "\t* #{workspace.name} - tags:"
22
+ client.tags.find_by_workspace(workspace: workspace.id).each do |tag|
23
+ puts "\t\t- #{tag.name}"
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'bundler'
3
+ Bundler.require
4
+ require 'asana'
5
+
6
+ api_token = ENV['ASANA_API_TOKEN']
7
+ unless api_token
8
+ abort "Run this program with the env var ASANA_API_TOKEN.\n" \
9
+ "Go to http://app.asana.com/-/account_api to see your API token."
10
+ end
11
+
12
+ client = Asana::Client.new do |c|
13
+ c.authentication :api_token, api_token
14
+ end
15
+
16
+ workspace = client.workspaces.find_all.first
17
+ task = client.tasks.find_all(assignee: "me", workspace: workspace.id).first
18
+ unless task
19
+ task = client.tasks.create(workspace: workspace.id, name: "Hello world!", assignee: "me")
20
+ end
21
+
22
+ Thread.abort_on_exception = true
23
+
24
+ Thread.new do
25
+ puts "Listening for 'changed' events on #{task} in one thread..."
26
+ task.events(wait: 2).lazy.select { |event| event.action == 'changed' }.each do |event|
27
+ puts "#{event.user.name} changed #{event.resource}"
28
+ end
29
+ end
30
+
31
+ Thread.new do
32
+ puts "Listening for non-'changed' events on #{task} in another thread..."
33
+ task.events(wait: 1).lazy.reject { |event| event.action == 'changed' }.each do |event|
34
+ puts "'#{event.action}' event: #{event}"
35
+ end
36
+ end
37
+
38
+ sleep
@@ -0,0 +1,54 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ require 'asana'
4
+
5
+ class SinatraApp < Sinatra::Base
6
+ id, secret = ENV['ASANA_CLIENT_ID'], ENV['ASANA_CLIENT_SECRET']
7
+ unless id && secret
8
+ abort "Run this program with the env vars ASANA_CLIENT_ID and ASANA_CLIENT_SECRET.\n" \
9
+ "Refer to https://asana.com/developers/documentation/getting-started/authentication "\
10
+ "to get your credentials."
11
+ end
12
+
13
+ use OmniAuth::Strategies::Asana, id, secret
14
+
15
+ enable :sessions
16
+
17
+ get '/' do
18
+ if $client
19
+ '<a href="/workspaces">My Workspaces</a>'
20
+ else
21
+ '<a href="/sign_in">sign in to asana</a>'
22
+ end
23
+ end
24
+
25
+ get '/workspaces' do
26
+ if $client
27
+ "<h1>My Workspaces</h1>" \
28
+ "<ul>" + $client.workspaces.find_all.map { |w| "<li>#{w.name}</li>" }.join + "</ul>"
29
+ else
30
+ redirect '/sign_in'
31
+ end
32
+ end
33
+
34
+ get '/auth/:name/callback' do
35
+ creds = request.env["omniauth.auth"]["credentials"].tap { |h| h.delete('expires') }
36
+ strategy = request.env["omniauth.strategy"]
37
+ access_token = OAuth2::AccessToken.from_hash(strategy.client, creds).refresh!
38
+ $client = Asana::Client.new do |c|
39
+ c.authentication :oauth2, access_token
40
+ end
41
+ redirect '/workspaces'
42
+ end
43
+
44
+ get '/sign_in' do
45
+ redirect '/auth/asana'
46
+ end
47
+
48
+ get '/sign_out' do
49
+ $client = nil
50
+ redirect '/'
51
+ end
52
+ end
53
+
54
+ SinatraApp.run! if __FILE__ == $0
data/lib/asana.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'asana/ruby2_0_0_compatibility'
2
+ require 'asana/authentication'
3
+ require 'asana/resources'
4
+ require 'asana/client'
5
+ require 'asana/errors'
6
+ require 'asana/http_client'
7
+ require 'asana/version'
8
+
9
+ # Public: Top-level namespace of the Asana API Ruby client.
10
+ module Asana
11
+ include Asana::Resources
12
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'authentication/oauth2'
2
+ require_relative 'authentication/token_authentication'
3
+
4
+ module Asana
5
+ # Public: Authentication strategies for the Asana API.
6
+ module Authentication
7
+ end
8
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'oauth2/bearer_token_authentication'
2
+ require_relative 'oauth2/access_token_authentication'
3
+ require_relative 'oauth2/client'
4
+
5
+ module Asana
6
+ module Authentication
7
+ # Public: Deals with OAuth2 authentication. Contains a function to get an
8
+ # access token throught a browserless authentication flow, needed for some
9
+ # applications such as CLI applications.
10
+ module OAuth2
11
+ module_function
12
+
13
+ # Public: Retrieves an access token through an offline authentication
14
+ # flow. If your application can receive HTTP requests, you might want to
15
+ # opt for a browser-based flow and use the omniauth-asana gem instead.
16
+ #
17
+ # Your registered application's redirect_uri should be exactly
18
+ # "urn:ietf:wg:oauth:2.0:oob".
19
+ #
20
+ # client_id - [String] the client id of the registered Asana API
21
+ # application.
22
+ # client_secret - [String] the client secret of the registered Asana API
23
+ # application.
24
+ #
25
+ # Returns an ::OAuth2::AccessToken object.
26
+ #
27
+ # Note: This function reads from STDIN and writes to STDOUT. It is meant
28
+ # to be used only within the context of a CLI application.
29
+ def offline_flow(client_id: required('client_id'),
30
+ client_secret: required('client_secret'))
31
+ client = Client.new(client_id: client_id,
32
+ client_secret: client_secret,
33
+ redirect_uri: 'urn:ietf:wg:oauth:2.0:oob')
34
+ STDOUT.puts '1. Go to the following URL to authorize the ' \
35
+ " application: #{client.authorize_url}"
36
+ STDOUT.puts '2. Paste the authorization code here: '
37
+ auth_code = STDIN.gets.chomp
38
+ client.token_from_auth_code(auth_code)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,51 @@
1
+ module Asana
2
+ module Authentication
3
+ module OAuth2
4
+ # Public: A mechanism to authenticate with an OAuth2 access token (a
5
+ # bearer token and a refresh token) or just a refresh token.
6
+ class AccessTokenAuthentication
7
+ # Public: Builds an AccessTokenAuthentication from a refresh token and
8
+ # client credentials, by refreshing into a new one.
9
+ #
10
+ # refresh_token - [String] a refresh token
11
+ # client_id - [String] the client id of the registered Asana API
12
+ # Application.
13
+ # client_secret - [String] the client secret of the registered Asana API
14
+ # Application.
15
+ # redirect_uri - [String] the redirect uri of the registered Asana API
16
+ # Application.
17
+ #
18
+ # Returns an [AccessTokenAuthentication] instance with a refreshed
19
+ # access token.
20
+ def self.from_refresh_token(refresh_token,
21
+ client_id: required('client_id'),
22
+ client_secret: required('client_secret'),
23
+ redirect_uri: required('redirect_uri'))
24
+ client = Client.new(client_id: client_id,
25
+ client_secret: client_secret,
26
+ redirect_uri: redirect_uri)
27
+ new(client.token_from_refresh_token(refresh_token))
28
+ end
29
+
30
+ # Public: Initializes a new AccessTokenAuthentication.
31
+ #
32
+ # access_token - [::OAuth2::AccessToken] An ::OAuth2::AccessToken
33
+ # object.
34
+ def initialize(access_token)
35
+ @token = access_token
36
+ end
37
+
38
+ # Public: Configures a Faraday connection injecting a bearer token,
39
+ # auto-refreshing it when needed.
40
+ #
41
+ # connection - [Faraday::Connection] the Faraday connection instance.
42
+ #
43
+ # Returns nothing.
44
+ def configure(connection)
45
+ @token = @token.refresh! if @token.expired?
46
+ connection.request :oauth2, @token.token
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ module Asana
2
+ module Authentication
3
+ module OAuth2
4
+ # Public: A mechanism to authenticate with an OAuth2 bearer token obtained
5
+ # somewhere, for instance through omniauth-asana.
6
+ #
7
+ # Note: This authentication mechanism doesn't support token refreshing. If
8
+ # you'd like refreshing and you have a refresh token as well as a bearer
9
+ # token, you can generate a proper access token with
10
+ # {AccessTokenAuthentication.from_refresh_token}.
11
+ class BearerTokenAuthentication
12
+ # Public: Initializes a new BearerTokenAuthentication with a plain
13
+ # bearer token.
14
+ #
15
+ # bearer_token - [String] a plain bearer token.
16
+ def initialize(bearer_token)
17
+ @token = bearer_token
18
+ end
19
+
20
+ # Public: Configures a Faraday connection injecting its token as an
21
+ # OAuth2 bearer token.
22
+ #
23
+ # connection - [Faraday::Connection] the Faraday connection instance.
24
+ #
25
+ # Returns nothing.
26
+ def configure(connection)
27
+ connection.request :oauth2, @token
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ require 'oauth2'
2
+
3
+ module Asana
4
+ module Authentication
5
+ module OAuth2
6
+ # Public: Deals with the details of obtaining an OAuth2 authorization URL
7
+ # and obtaining access tokens from either authorization codes or refresh
8
+ # tokens.
9
+ class Client
10
+ # Public: Initializes a new client with client credentials associated
11
+ # with a registered Asana API application.
12
+ #
13
+ # client_id - [String] a client id from the registered application
14
+ # client_secret - [String] a client secret from the registered
15
+ # application
16
+ # redirect_uri - [String] a redirect uri from the registered
17
+ # application
18
+ def initialize(client_id: required('client_id'),
19
+ client_secret: required('client_secret'),
20
+ redirect_uri: required('redirect_uri'))
21
+ @client = ::OAuth2::Client.new(client_id, client_secret,
22
+ site: 'https://app.asana.com',
23
+ authorize_url: '/-/oauth_authorize',
24
+ token_url: '/-/oauth_token')
25
+ @redirect_uri = redirect_uri
26
+ end
27
+
28
+ # Public:
29
+ # Returns the [String] OAuth2 authorize URL.
30
+ def authorize_url
31
+ @client.auth_code.authorize_url(redirect_uri: @redirect_uri)
32
+ end
33
+
34
+ # Public: Retrieves a token from an authorization code.
35
+ #
36
+ # Returns the [::OAuth2::AccessToken] token.
37
+ def token_from_auth_code(auth_code)
38
+ @client.auth_code.get_token(auth_code, redirect_uri: @redirect_uri)
39
+ end
40
+
41
+ # Public: Retrieves a token from a refresh token.
42
+ #
43
+ # Returns the refreshed [::OAuth2::AccessToken] token.
44
+ def token_from_refresh_token(token)
45
+ ::OAuth2::AccessToken.new(@client, '', refresh_token: token).refresh!
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end