orcid 0.0.4 → 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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -0
  3. data/.hound.yml +793 -0
  4. data/.travis.yml +14 -0
  5. data/Gemfile +14 -0
  6. data/README.md +107 -6
  7. data/Rakefile +17 -9
  8. data/app/assets/images/orcid/.keep +0 -0
  9. data/app/controllers/orcid/application_controller.rb +13 -4
  10. data/app/controllers/orcid/profile_connections_controller.rb +34 -6
  11. data/app/controllers/orcid/profile_requests_controller.rb +18 -15
  12. data/app/models/orcid/profile.rb +15 -5
  13. data/app/models/orcid/profile_connection.rb +20 -20
  14. data/app/models/orcid/profile_request.rb +39 -20
  15. data/app/models/orcid/work.rb +45 -55
  16. data/app/models/orcid/work/xml_parser.rb +45 -0
  17. data/app/models/orcid/work/xml_renderer.rb +29 -0
  18. data/app/services/orcid/remote/profile_creation_service.rb +40 -32
  19. data/app/services/orcid/remote/profile_query_service.rb +82 -0
  20. data/app/services/orcid/remote/profile_query_service/query_parameter_builder.rb +43 -0
  21. data/app/services/orcid/remote/profile_query_service/search_response.rb +31 -0
  22. data/app/services/orcid/remote/service.rb +16 -13
  23. data/app/services/orcid/remote/work_service.rb +58 -43
  24. data/app/templates/orcid/work.template.v1.1.xml.erb +32 -1
  25. data/app/views/orcid/profile_connections/_orcid_connector.html.erb +14 -0
  26. data/app/views/orcid/profile_connections/new.html.erb +4 -4
  27. data/bin/rails +8 -0
  28. data/config/{application.yml → application.yml.example} +3 -13
  29. data/config/locales/orcid.en.yml +5 -1
  30. data/config/routes.rb +4 -2
  31. data/lib/generators/orcid/install/install_generator.rb +46 -7
  32. data/lib/orcid.rb +23 -11
  33. data/lib/orcid/configuration.rb +32 -13
  34. data/lib/orcid/configuration/provider.rb +27 -13
  35. data/lib/orcid/engine.rb +20 -4
  36. data/lib/orcid/exceptions.rb +33 -4
  37. data/lib/orcid/named_callbacks.rb +3 -1
  38. data/lib/orcid/spec_support.rb +19 -9
  39. data/lib/orcid/version.rb +1 -1
  40. data/lib/tasks/orcid_tasks.rake +3 -3
  41. data/orcid.gemspec +51 -0
  42. data/rubocop.txt +1164 -0
  43. data/script/fast_specs +22 -0
  44. data/spec/controllers/orcid/profile_connections_controller_spec.rb +101 -0
  45. data/spec/controllers/orcid/profile_requests_controller_spec.rb +116 -0
  46. data/spec/factories/orcid_profile_requests.rb +11 -0
  47. data/spec/factories/users.rb +9 -0
  48. data/spec/fast_helper.rb +12 -0
  49. data/spec/features/batch_profile_spec.rb +31 -0
  50. data/spec/features/non_ui_based_interactions_spec.rb +117 -0
  51. data/spec/features/profile_connection_feature_spec.rb +19 -0
  52. data/spec/features/public_api_query_spec.rb +36 -0
  53. data/spec/fixtures/orcid_works.xml +55 -0
  54. data/spec/lib/orcid/configuration/provider_spec.rb +40 -0
  55. data/spec/lib/orcid/configuration_spec.rb +38 -0
  56. data/spec/lib/orcid/named_callbacks_spec.rb +28 -0
  57. data/spec/lib/orcid_spec.rb +97 -0
  58. data/spec/models/orcid/profile_connection_spec.rb +81 -0
  59. data/spec/models/orcid/profile_request_spec.rb +131 -0
  60. data/spec/models/orcid/profile_spec.rb +76 -0
  61. data/spec/models/orcid/work/xml_parser_spec.rb +40 -0
  62. data/spec/models/orcid/work/xml_renderer_spec.rb +18 -0
  63. data/spec/models/orcid/work_spec.rb +53 -0
  64. data/spec/services/orcid/remote/profile_creation_service_spec.rb +40 -0
  65. data/spec/services/orcid/remote/profile_query_service/query_parameter_builder_spec.rb +44 -0
  66. data/spec/services/orcid/remote/profile_query_service/search_response_spec.rb +14 -0
  67. data/spec/services/orcid/remote/profile_query_service_spec.rb +118 -0
  68. data/spec/services/orcid/remote/service_spec.rb +26 -0
  69. data/spec/services/orcid/remote/work_service_spec.rb +44 -0
  70. data/spec/spec_helper.rb +99 -0
  71. data/spec/support/non_orcid_models.rb +11 -0
  72. data/spec/support/stub_callback.rb +25 -0
  73. data/spec/test_app_templates/Gemfile.extra +3 -0
  74. data/spec/test_app_templates/lib/generators/test_app_generator.rb +36 -0
  75. data/spec/views/orcid/profile_connections/new.html.erb_spec.rb +25 -0
  76. data/spec/views/orcid/profile_requests/new.html.erb_spec.rb +23 -0
  77. metadata +119 -29
  78. data/app/runners/orcid/profile_lookup_runner.rb +0 -33
  79. data/app/runners/orcid/runner.rb +0 -15
  80. data/app/services/orcid/remote/profile_lookup_service.rb +0 -56
  81. data/app/services/orcid/remote/profile_lookup_service/search_response.rb +0 -23
  82. data/lib/orcid/query_parameter_builder.rb +0 -38
data/lib/orcid.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'orcid/engine'
2
2
  require 'orcid/configuration'
3
3
  require 'orcid/exceptions'
4
-
4
+ require 'figaro'
5
5
  require 'mappy'
6
6
  require 'devise_multi_auth'
7
7
  require 'virtus'
@@ -9,8 +9,8 @@ require 'omniauth-orcid'
9
9
  require 'email_validator'
10
10
  require 'simple_form'
11
11
 
12
+ # The namespace for all things related to Orcid integration
12
13
  module Orcid
13
-
14
14
  class << self
15
15
  attr_writer :configuration
16
16
 
@@ -32,26 +32,39 @@ module Orcid
32
32
  configuration.provider
33
33
  end
34
34
 
35
+ def parent_controller
36
+ configuration.parent_controller
37
+ end
38
+
35
39
  def authentication_model
36
40
  configuration.authentication_model
37
41
  end
38
42
 
39
43
  def connect_user_and_orcid_profile(user, orcid_profile_id)
40
- authentication_model.create!(provider: 'orcid', uid: orcid_profile_id, user: user)
44
+ authentication_model.create!(
45
+ provider: 'orcid', uid: orcid_profile_id, user: user
46
+ )
41
47
  end
42
48
 
43
49
  def access_token_for(orcid_profile_id, options = {})
44
50
  client = options.fetch(:client) { oauth_client }
45
51
  tokenizer = options.fetch(:tokenizer) { authentication_model }
46
- tokenizer.to_access_token(uid: orcid_profile_id, provider: 'orcid', client: client)
52
+ tokenizer.to_access_token(
53
+ uid: orcid_profile_id, provider: 'orcid', client: client
54
+ )
55
+ end
56
+
57
+ # Returns true if the person with the given ORCID has already obtained an
58
+ # ORCID access token by authenticating via ORCID.
59
+ def authenticated_orcid?(orcid_profile_id)
60
+ Orcid.access_token_for(orcid_profile_id).present?
61
+ rescue Devise::MultiAuth::AccessTokenError
62
+ return false
47
63
  end
48
64
 
49
65
  def profile_for(user)
50
- if auth = authentication_model.where(provider: 'orcid', user: user).first
51
- Orcid::Profile.new(auth.uid)
52
- else
53
- nil
54
- end
66
+ auth = authentication_model.where(provider: 'orcid', user: user).first
67
+ auth && Orcid::Profile.new(auth.uid)
55
68
  end
56
69
 
57
70
  def enqueue(object)
@@ -60,7 +73,7 @@ module Orcid
60
73
 
61
74
  def oauth_client
62
75
  # passing the site: option as Orcid's Sandbox has an invalid certificate
63
- # for the api.sandbox-1.orcid.org
76
+ # for the api.sandbox.orcid.org
64
77
  @oauth_client ||= Devise::MultiAuth.oauth_client_for(
65
78
  'orcid', options: { site: provider.site_url }
66
79
  )
@@ -70,5 +83,4 @@ module Orcid
70
83
  tokenizer = options.fetch(:tokenizer) { oauth_client.client_credentials }
71
84
  tokenizer.get_token(scope: scope)
72
85
  end
73
-
74
86
  end
@@ -1,28 +1,47 @@
1
1
  module Orcid
2
+ # Responsible for exposing the customization mechanism
2
3
  class Configuration
3
4
  attr_reader :mapper
4
5
  def initialize(options = {})
5
- @mapper = options.fetch(:mapper) {
6
- require 'mappy'
7
- ::Mappy
8
- }
9
- @provider = options.fetch(:provider) {
10
- require 'orcid/configuration/provider'
11
- Provider.new
12
- }
13
- @authentication_model = options.fetch(:authentication_model) {
14
- require 'devise-multi_auth'
15
- ::Devise::MultiAuth::Authentication
16
- }
6
+ @mapper = options.fetch(:mapper) { default_mapper }
7
+ @provider = options.fetch(:provider) { default_provider }
8
+ @authentication_model = options.fetch(:authentication_model) do
9
+ default_authenticaton_model
10
+ end
11
+ @parent_controller = options.fetch(:parent_controller) do
12
+ '::ApplicationController'
13
+ end
17
14
  end
18
15
 
19
16
  attr_accessor :provider
20
17
  attr_accessor :authentication_model
18
+ attr_accessor :parent_controller
21
19
 
22
20
  def register_mapping_to_orcid_work(source_type, legend)
23
21
  mapper.configure do |config|
24
- config.register(source: source_type, target: 'orcid/work', legend: legend)
22
+ config.register(
23
+ source: source_type,
24
+ target: 'orcid/work',
25
+ legend: legend
26
+ )
25
27
  end
26
28
  end
29
+
30
+ protected
31
+
32
+ def default_mapper
33
+ require 'mappy'
34
+ ::Mappy
35
+ end
36
+
37
+ def default_provider
38
+ require 'orcid/configuration/provider'
39
+ Provider.new
40
+ end
41
+
42
+ def default_authenticaton_model
43
+ require 'devise-multi_auth'
44
+ ::Devise::MultiAuth::Authentication
45
+ end
27
46
  end
28
47
  end
@@ -1,5 +1,10 @@
1
+ require 'orcid/exceptions'
1
2
  module Orcid
2
3
  class Configuration
4
+ # Responsible for negotiating the retrieval of Orcid provider information.
5
+ # Especially important given that you have private auth keys.
6
+ # Also given that you may want to request against the sandbox versus the
7
+ # production Orcid service.
3
8
  class Provider
4
9
  attr_reader :store
5
10
  def initialize(store = ::ENV)
@@ -8,43 +13,52 @@ module Orcid
8
13
 
9
14
  attr_writer :authentication_scope
10
15
  def authentication_scope
11
- @authentication_scope ||= store.fetch('ORCID_APP_AUTHENTICATION_SCOPE') {
12
- "/authenticate,/orcid-works/create,/orcid-works/update,/read-public"
13
- }
16
+ @authentication_scope ||=
17
+ store.fetch('ORCID_APP_AUTHENTICATION_SCOPE') do
18
+ '/authenticate,/orcid-works/create,/orcid-works/update,/read-public'
19
+ end
14
20
  end
15
21
 
16
22
  attr_writer :site_url
17
23
  def site_url
18
- @site_url ||= store.fetch('ORCID_SITE_URL') { "http://api.sandbox-1.orcid.org" }
24
+ @site_url ||= store.fetch('ORCID_SITE_URL') do
25
+ 'http://api.sandbox.orcid.org'
26
+ end
19
27
  end
20
28
 
21
29
  attr_writer :token_url
22
30
  def token_url
23
- @token_url ||= store.fetch('ORCID_TOKEN_URL') { "https://api.sandbox-1.orcid.org/oauth/token" }
31
+ @token_url ||= store.fetch('ORCID_TOKEN_URL') do
32
+ 'https://api.sandbox.orcid.org/oauth/token'
33
+ end
24
34
  end
25
35
 
26
36
  attr_writer :signin_via_json_url
27
37
  def signin_via_json_url
28
- @signin_via_json_url ||= store.fetch('ORCID_REMOTE_SIGNIN_URL') { "https://sandbox-1.orcid.org/signin/auth.json" }
38
+ @signin_via_json_url ||= store.fetch('ORCID_REMOTE_SIGNIN_URL') do
39
+ 'https://sandbox.orcid.org/signin/auth.json'
40
+ end
29
41
  end
30
42
 
31
43
  attr_writer :authorize_url
32
44
  def authorize_url
33
- @authorize_url ||= store.fetch('ORCID_AUTHORIZE_URL') { "https://sandbox-1.orcid.org/oauth/authorize" }
45
+ @authorize_url ||= store.fetch('ORCID_AUTHORIZE_URL') do
46
+ 'https://sandbox.orcid.org/oauth/authorize'
47
+ end
34
48
  end
35
49
 
36
50
  attr_writer :id
37
51
  def id
38
- @id ||= store.fetch('ORCID_APP_ID') {
39
- Rails.logger.error('Set your Orcid.provider.id')
40
- }
52
+ @id ||= store.fetch('ORCID_APP_ID')
53
+ rescue KeyError
54
+ raise ConfigurationError, 'ORCID_APP_ID'
41
55
  end
42
56
 
43
57
  attr_writer :secret
44
58
  def secret
45
- @secret ||= store.fetch('ORCID_APP_SECRET') {
46
- Rails.logger.error('Set your Orcid.provider.secret')
47
- }
59
+ @secret ||= store.fetch('ORCID_APP_SECRET')
60
+ rescue KeyError
61
+ raise ConfigurationError, 'ORCID_APP_SECRET'
48
62
  end
49
63
  end
50
64
  end
data/lib/orcid/engine.rb CHANGED
@@ -1,15 +1,31 @@
1
+ # The namespace for all things related to Orcid integration
1
2
  module Orcid
3
+ module_function
4
+
5
+ # As per an isolated_namespace Rails engine.
6
+ # But the isolated namespace creates issues.
7
+ # @api private
8
+ def table_name_prefix
9
+ 'orcid_'
10
+ end
11
+
12
+ # Because I am not using isolate_namespace for Orcid::Engine
13
+ # I need this for the application router to find the appropriate routes.
14
+ # @api private
15
+ def use_relative_model_naming?
16
+ true
17
+ end
18
+
19
+ # While not an isolated namespace engine
20
+ # @See http://guides.rubyonrails.org/engines.html
2
21
  class Engine < ::Rails::Engine
3
- isolate_namespace Orcid
22
+ engine_name 'orcid'
4
23
 
5
24
  initializer 'orcid.initializers' do |app|
6
25
  app.config.paths.add 'app/services', eager_load: true
7
- app.config.paths.add 'app/runners', eager_load: true
8
26
  app.config.autoload_paths += %W(
9
27
  #{config.root}/app/services
10
- #{config.root}/app/runners
11
28
  )
12
29
  end
13
-
14
30
  end
15
31
  end
@@ -1,11 +1,28 @@
1
1
  module Orcid
2
+
3
+ class ConfigurationError < RuntimeError
4
+ def initialize(key_name)
5
+ super("Unable to find #{key_name.inspect} in configuration storage.")
6
+ end
7
+ end
8
+
2
9
  # Because in trouble shooting what all goes into this remote call,
3
10
  # you may very well want all of this.
4
11
  class RemoteServiceError < RuntimeError
5
12
  def initialize(options)
6
13
  text = []
7
14
  text << "-- Client --"
8
- if client = options[:client]
15
+ append_client_options(options[:client], text)
16
+ append_token(options[:token], text)
17
+ append_request(options, text)
18
+ append_response(options, text)
19
+ super(text.join("\n"))
20
+ end
21
+
22
+ private
23
+
24
+ def append_client_options(client, text)
25
+ if client
9
26
  text << "id:\n\t#{client.id.inspect}"
10
27
  text << "site:\n\t#{client.site.inspect}"
11
28
  text << "options:\n\t#{client.options.inspect}"
@@ -13,19 +30,31 @@ module Orcid
13
30
  text << "scopes:\n\t#{Orcid.provider.authentication_scope}"
14
31
  end
15
32
  end
33
+ text
34
+ end
35
+
36
+ def append_token(token, text)
16
37
  text << "\n-- Token --"
17
- if token = options[:token]
38
+ if token
18
39
  text << "access_token:\n\t#{token.token.inspect}"
19
40
  text << "refresh_token:\n\t#{token.refresh_token.inspect}"
20
41
  end
42
+ text
43
+ end
44
+
45
+ def append_request(options, text)
21
46
  text << "\n-- Request --"
22
47
  text << "path:\n\t#{options[:request_path].inspect}" if options[:request_path]
23
48
  text << "headers:\n\t#{options[:request_headers].inspect}" if options[:request_headers]
24
49
  text << "body:\n\t#{options[:request_body]}" if options[:request_body]
50
+ text
51
+ end
52
+
53
+ def append_response(options, text)
25
54
  text << "\n-- Response --"
26
55
  text << "status:\n\t#{options[:response_status].inspect}" if options[:response_status]
27
56
  text << "body:\n\t#{options[:response_body]}" if options[:response_body]
28
- super(text.join("\n"))
57
+ text
29
58
  end
30
59
  end
31
- end
60
+ end
@@ -6,7 +6,9 @@ module Orcid
6
6
  @callbacks = {}
7
7
  end
8
8
 
9
- def method_missing(callback_name, *args, &block)
9
+ # Note this very specific implementation of #method_missing will raise
10
+ # errors on non-zero method arity.
11
+ def method_missing(callback_name, &block)
10
12
  @callbacks[callback_name] = block
11
13
  end
12
14
 
@@ -19,7 +19,7 @@ class RequestSandboxAuthorizationCode
19
19
 
20
20
  def call(options = {})
21
21
  orcid_profile_id = options.fetch(:orcid_profile_id) { ENV['ORCID_CLAIMED_PROFILE_ID'] }
22
- password = options.fetch(:password) { ENV['ORCID_CLAIMED_PROFILE_PASSWORD']}
22
+ password = options.fetch(:password) { ENV['ORCID_CLAIMED_PROFILE_PASSWORD'] }
23
23
 
24
24
  login_to_orcid(orcid_profile_id, password)
25
25
  request_authorization
@@ -30,25 +30,35 @@ class RequestSandboxAuthorizationCode
30
30
  private :cookies
31
31
 
32
32
  private
33
+
33
34
  def login_to_orcid(orcid_profile_id, password)
34
- response = RestClient.post(login_url, userId: orcid_profile_id, password: password )
35
- if ! JSON.parse(response)["success"]
36
- raise "Response not successful: \n#{response}"
37
- else
35
+ response = RestClient.post(
36
+ login_url, userId: orcid_profile_id, password: password
37
+ )
38
+ if JSON.parse(response)['success']
38
39
  self.cookies = response.cookies
40
+ else
41
+ fail "Response not successful: \n#{response}"
39
42
  end
40
43
  end
41
44
 
42
45
  def request_authorization
43
- parameters = { client_id: orcid_client_id, response_type: 'code', scope: access_scope, redirect_uri: oauth_redirect_uri }
44
- RestClient.get(authorize_url, {params: parameters, cookies: cookies})
46
+ parameters = {
47
+ client_id: orcid_client_id,
48
+ response_type: 'code',
49
+ scope: access_scope,
50
+ redirect_uri: oauth_redirect_uri
51
+ }
52
+ RestClient.get(authorize_url, { params: parameters, cookies: cookies })
45
53
  end
46
54
 
47
55
  def request_authorization_code
48
- RestClient.post(authorize_url, {user_oauth_approval: true}, {cookies: cookies})
56
+ RestClient.post(
57
+ authorize_url, { user_oauth_approval: true }, { cookies: cookies }
58
+ )
49
59
  rescue RestClient::Found => e
50
60
  uri = URI.parse(e.response.headers.fetch(:location))
51
- CGI::parse(uri.query).fetch('code').first
61
+ CGI.parse(uri.query).fetch('code').first
52
62
  end
53
63
 
54
64
  end
data/lib/orcid/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Orcid
2
- VERSION = "0.0.4"
2
+ VERSION = '0.1.0'
3
3
  end
@@ -6,7 +6,7 @@ namespace :orcid do
6
6
  task :query_runner do
7
7
  @runner = lambda { |person|
8
8
  puts "Processing: #{person.email}"
9
- Orcid::ProfileLookupRunner.new {|on|
9
+ Orcid::Remote::ProfileLookupRunner.new {|on|
10
10
  on.found {|results|
11
11
  person.found(results)
12
12
  puts "\tfound" if verbose
@@ -39,7 +39,7 @@ namespace :orcid do
39
39
  }
40
40
  @runner = lambda { |person|
41
41
  puts "Processing: #{person.email}"
42
- Orcid::ProfileLookupRunner.new {|on|
42
+ Orcid::Remote::ProfileQueryService.new {|on|
43
43
  on.found {|results|
44
44
  person.found(results)
45
45
  puts "\tfound" if verbose
@@ -92,7 +92,7 @@ namespace :orcid do
92
92
  end
93
93
 
94
94
  def found(existing_orcids)
95
- @existing_orcids = Array(existing_orcids).collect(&:orcid_profile_id).join("; ")
95
+ @existing_orcids = Array.wrap(existing_orcids).collect(&:orcid_profile_id).join("; ")
96
96
  end
97
97
 
98
98
  def to_output_row
data/orcid.gemspec ADDED
@@ -0,0 +1,51 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+
3
+ # Maintain your gem's version:
4
+ require 'orcid/version'
5
+
6
+ # Describe your gem and declare its dependencies:
7
+ Gem::Specification.new do |s|
8
+ s.name = 'orcid'
9
+ s.version = Orcid::VERSION
10
+ s.authors = [
11
+ 'Jeremy Friesen'
12
+ ]
13
+ s.email = [
14
+ 'jeremy.n.friesen@gmail.com'
15
+ ]
16
+ s.homepage = 'https://github.com/jeremyf/orcid'
17
+ s.summary = 'A Rails engine for orcid.org integration.'
18
+ s.description = 'A Rails engine for orcid.org integration.'
19
+
20
+ s.files = `git ls-files -z`.split("\x0")
21
+ # Deliberately removing bin executables as it appears to relate to
22
+ # https://github.com/cbeer/engine_cart/issues/9
23
+ s.executables = s.executables = s.files.grep(%r{^bin/}) do |f|
24
+ f == 'bin/rails' ? nil : File.basename(f)
25
+ end.compact
26
+ s.test_files = s.files.grep(/^(test|spec|features)\//)
27
+ s.require_paths = ['lib']
28
+
29
+ s.add_dependency 'rails', '~> 4.0.3'
30
+ s.add_dependency 'mappy', '~> 0.1.0'
31
+ s.add_dependency 'figaro'
32
+ s.add_dependency 'devise-multi_auth', '~> 0.1.0'
33
+ s.add_dependency 'omniauth-orcid'
34
+ s.add_dependency 'virtus'
35
+ s.add_dependency 'email_validator'
36
+ s.add_dependency 'simple_form'
37
+ s.add_dependency 'byebug'
38
+
39
+ s.add_development_dependency 'sqlite3'
40
+ s.add_development_dependency 'engine_cart'
41
+ s.add_development_dependency 'rspec-rails'
42
+ s.add_development_dependency 'database_cleaner'
43
+ s.add_development_dependency 'factory_girl'
44
+ s.add_development_dependency 'rspec-html-matchers'
45
+ s.add_development_dependency 'capybara'
46
+ s.add_development_dependency 'capybara-webkit'
47
+ s.add_development_dependency 'webmock'
48
+ s.add_development_dependency 'simplecov'
49
+ s.add_development_dependency 'rest_client'
50
+ s.add_development_dependency 'rspec-given'
51
+ end