openc-asana 0.1.2

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