shopify-cli 1.3.0 → 1.6.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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -2
  3. data/.github/CONTRIBUTING.md +9 -1
  4. data/.github/PULL_REQUEST_TEMPLATE.md +10 -1
  5. data/.github/workflows/release.yml +61 -0
  6. data/.github/workflows/triage.yml +22 -0
  7. data/.gitignore +0 -1
  8. data/.rubocop.yml +61 -8
  9. data/.rubocop_todo.yml +11 -0
  10. data/.travis.yml +1 -0
  11. data/CHANGELOG.md +30 -0
  12. data/Gemfile +3 -2
  13. data/Gemfile.lock +39 -37
  14. data/README.md +39 -7
  15. data/RELEASING.md +19 -29
  16. data/Rakefile +2 -0
  17. data/dev.yml +2 -2
  18. data/docs/_config.yml +1 -18
  19. data/docs/app/node/commands/index.md +2 -80
  20. data/docs/app/node/index.md +2 -33
  21. data/docs/app/rails/commands/index.md +2 -78
  22. data/docs/app/rails/index.md +2 -34
  23. data/docs/core/index.md +2 -84
  24. data/docs/getting-started/index.md +2 -25
  25. data/docs/getting-started/install/index.md +1 -118
  26. data/docs/getting-started/migrate/index.md +2 -94
  27. data/docs/getting-started/uninstall/index.md +2 -35
  28. data/docs/getting-started/upgrade/index.md +2 -39
  29. data/docs/help/start-app/index.md +2 -4
  30. data/docs/index.md +2 -24
  31. data/install.sh +1 -1
  32. data/lib/project_types/extension/cli.rb +21 -11
  33. data/lib/project_types/extension/commands/extension_command.rb +2 -2
  34. data/lib/project_types/extension/features/argo.rb +117 -0
  35. data/lib/project_types/extension/forms/create.rb +2 -2
  36. data/lib/project_types/extension/models/specification.rb +35 -0
  37. data/lib/project_types/extension/models/specification_handlers/checkout_post_purchase.rb +19 -0
  38. data/lib/project_types/extension/models/specification_handlers/default.rb +67 -0
  39. data/lib/project_types/extension/models/specifications.rb +77 -0
  40. data/lib/project_types/extension/tasks/configure_features.rb +52 -0
  41. data/lib/project_types/extension/tasks/fetch_specifications.rb +38 -0
  42. data/lib/project_types/node/cli.rb +4 -1
  43. data/lib/project_types/node/commands/connect.rb +15 -0
  44. data/lib/project_types/node/commands/create.rb +10 -4
  45. data/lib/project_types/node/commands/generate.rb +2 -11
  46. data/lib/project_types/node/messages/messages.rb +16 -50
  47. data/lib/project_types/rails/cli.rb +4 -1
  48. data/lib/project_types/rails/commands/connect.rb +15 -0
  49. data/lib/project_types/rails/commands/create.rb +15 -12
  50. data/lib/project_types/rails/forms/create.rb +1 -1
  51. data/lib/project_types/rails/gem.rb +1 -1
  52. data/lib/project_types/rails/messages/messages.rb +8 -5
  53. data/lib/project_types/script/cli.rb +9 -5
  54. data/lib/project_types/script/commands/create.rb +6 -4
  55. data/lib/project_types/script/commands/enable.rb +12 -4
  56. data/lib/project_types/script/commands/push.rb +5 -13
  57. data/lib/project_types/script/config/extension_points.yml +17 -12
  58. data/lib/project_types/script/errors.rb +21 -0
  59. data/lib/project_types/script/forms/create.rb +26 -2
  60. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +10 -1
  61. data/lib/project_types/script/layers/application/build_script.rb +18 -17
  62. data/lib/project_types/script/layers/application/create_script.rb +12 -10
  63. data/lib/project_types/script/layers/application/extension_points.rb +24 -0
  64. data/lib/project_types/script/layers/application/push_script.rb +18 -16
  65. data/lib/project_types/script/layers/domain/errors.rb +7 -0
  66. data/lib/project_types/script/layers/domain/extension_point.rb +62 -7
  67. data/lib/project_types/script/layers/domain/metadata.rb +55 -0
  68. data/lib/project_types/script/layers/domain/push_package.rb +25 -6
  69. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +17 -52
  70. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +42 -11
  71. data/lib/project_types/script/layers/infrastructure/errors.rb +16 -0
  72. data/lib/project_types/script/layers/infrastructure/extension_point_repository.rb +10 -4
  73. data/lib/project_types/script/layers/infrastructure/project_creator.rb +2 -1
  74. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +25 -13
  75. data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +72 -0
  76. data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +59 -0
  77. data/lib/project_types/script/layers/infrastructure/script_service.rb +9 -1
  78. data/lib/project_types/script/layers/infrastructure/task_runner.rb +4 -3
  79. data/lib/project_types/script/messages/messages.rb +55 -4
  80. data/lib/project_types/script/script_project.rb +25 -16
  81. data/lib/project_types/script/ui/error_handler.rb +59 -1
  82. data/lib/project_types/theme/cli.rb +40 -0
  83. data/lib/project_types/theme/commands/connect.rb +54 -0
  84. data/lib/project_types/theme/commands/create.rb +48 -0
  85. data/lib/project_types/theme/commands/deploy.rb +38 -0
  86. data/lib/project_types/theme/commands/generate.rb +20 -0
  87. data/lib/project_types/theme/commands/generate/env.rb +79 -0
  88. data/lib/project_types/theme/commands/push.rb +55 -0
  89. data/lib/project_types/theme/commands/serve.rb +31 -0
  90. data/lib/project_types/theme/forms/connect.rb +34 -0
  91. data/lib/project_types/theme/forms/create.rb +22 -0
  92. data/lib/project_types/theme/messages/messages.rb +147 -0
  93. data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +78 -0
  94. data/lib/project_types/theme/themekit.rb +113 -0
  95. data/lib/shopify-cli/admin_api.rb +42 -2
  96. data/lib/shopify-cli/api.rb +34 -33
  97. data/lib/shopify-cli/commands/config.rb +24 -0
  98. data/lib/shopify-cli/commands/connect.rb +32 -15
  99. data/lib/shopify-cli/commands/system.rb +10 -1
  100. data/lib/shopify-cli/context.rb +23 -2
  101. data/lib/shopify-cli/core/entry_point.rb +1 -1
  102. data/lib/shopify-cli/core/monorail.rb +6 -4
  103. data/lib/shopify-cli/feature.rb +0 -2
  104. data/lib/shopify-cli/http_request.rb +27 -0
  105. data/lib/shopify-cli/js_deps.rb +1 -1
  106. data/lib/shopify-cli/messages/messages.rb +31 -7
  107. data/lib/shopify-cli/method_object.rb +104 -0
  108. data/lib/shopify-cli/partners_api.rb +25 -3
  109. data/lib/shopify-cli/process_supervision.rb +1 -1
  110. data/lib/shopify-cli/project.rb +12 -8
  111. data/lib/shopify-cli/project_type.rb +18 -2
  112. data/lib/shopify-cli/resolve_constant.rb +25 -0
  113. data/lib/shopify-cli/result.rb +432 -0
  114. data/lib/shopify-cli/shopifolk.rb +87 -0
  115. data/lib/shopify-cli/task.rb +8 -0
  116. data/lib/shopify-cli/tasks/create_api_client.rb +13 -2
  117. data/lib/shopify-cli/tasks/ensure_env.rb +3 -0
  118. data/lib/shopify-cli/tasks/select_org_and_shop.rb +10 -5
  119. data/lib/shopify-cli/tunnel.rb +8 -2
  120. data/lib/shopify-cli/version.rb +1 -1
  121. data/lib/shopify_cli.rb +5 -1
  122. data/shopify.fish +1 -1
  123. data/shopify.sh +1 -1
  124. data/vendor/deps/cli-kit/REVISION +1 -1
  125. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +2 -2
  126. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +3 -3
  127. data/vendor/deps/cli-ui/REVISION +1 -1
  128. data/vendor/deps/cli-ui/lib/cli/ui.rb +26 -22
  129. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +4 -6
  130. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +3 -3
  131. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +8 -9
  132. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +1 -1
  133. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +1 -0
  134. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +15 -3
  135. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +4 -11
  136. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +3 -5
  137. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +10 -10
  138. data/vendor/deps/cli-ui/lib/cli/ui/version.rb +1 -1
  139. data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +56 -0
  140. data/vendor/deps/webrick/.gitignore +9 -0
  141. data/vendor/deps/webrick/Gemfile +3 -0
  142. data/vendor/deps/webrick/LICENSE.txt +22 -0
  143. data/vendor/deps/webrick/README.md +61 -0
  144. data/vendor/deps/webrick/Rakefile +10 -0
  145. data/vendor/deps/webrick/lib/webrick.rb +232 -0
  146. data/vendor/deps/webrick/lib/webrick/accesslog.rb +157 -0
  147. data/vendor/deps/webrick/lib/webrick/cgi.rb +313 -0
  148. data/vendor/deps/webrick/lib/webrick/compat.rb +36 -0
  149. data/vendor/deps/webrick/lib/webrick/config.rb +158 -0
  150. data/vendor/deps/webrick/lib/webrick/cookie.rb +172 -0
  151. data/vendor/deps/webrick/lib/webrick/htmlutils.rb +30 -0
  152. data/vendor/deps/webrick/lib/webrick/httpauth.rb +96 -0
  153. data/vendor/deps/webrick/lib/webrick/httpauth/authenticator.rb +117 -0
  154. data/vendor/deps/webrick/lib/webrick/httpauth/basicauth.rb +116 -0
  155. data/vendor/deps/webrick/lib/webrick/httpauth/digestauth.rb +395 -0
  156. data/vendor/deps/webrick/lib/webrick/httpauth/htdigest.rb +132 -0
  157. data/vendor/deps/webrick/lib/webrick/httpauth/htgroup.rb +97 -0
  158. data/vendor/deps/webrick/lib/webrick/httpauth/htpasswd.rb +158 -0
  159. data/vendor/deps/webrick/lib/webrick/httpauth/userdb.rb +53 -0
  160. data/vendor/deps/webrick/lib/webrick/httpproxy.rb +354 -0
  161. data/vendor/deps/webrick/lib/webrick/httprequest.rb +636 -0
  162. data/vendor/deps/webrick/lib/webrick/httpresponse.rb +564 -0
  163. data/vendor/deps/webrick/lib/webrick/https.rb +152 -0
  164. data/vendor/deps/webrick/lib/webrick/httpserver.rb +294 -0
  165. data/vendor/deps/webrick/lib/webrick/httpservlet.rb +23 -0
  166. data/vendor/deps/webrick/lib/webrick/httpservlet/abstract.rb +152 -0
  167. data/vendor/deps/webrick/lib/webrick/httpservlet/cgi_runner.rb +47 -0
  168. data/vendor/deps/webrick/lib/webrick/httpservlet/cgihandler.rb +126 -0
  169. data/vendor/deps/webrick/lib/webrick/httpservlet/erbhandler.rb +88 -0
  170. data/vendor/deps/webrick/lib/webrick/httpservlet/filehandler.rb +552 -0
  171. data/vendor/deps/webrick/lib/webrick/httpservlet/prochandler.rb +47 -0
  172. data/vendor/deps/webrick/lib/webrick/httpstatus.rb +194 -0
  173. data/vendor/deps/webrick/lib/webrick/httputils.rb +512 -0
  174. data/vendor/deps/webrick/lib/webrick/httpversion.rb +76 -0
  175. data/vendor/deps/webrick/lib/webrick/log.rb +156 -0
  176. data/vendor/deps/webrick/lib/webrick/server.rb +381 -0
  177. data/vendor/deps/webrick/lib/webrick/ssl.rb +215 -0
  178. data/vendor/deps/webrick/lib/webrick/utils.rb +265 -0
  179. data/vendor/deps/webrick/lib/webrick/version.rb +18 -0
  180. data/vendor/deps/webrick/webrick.gemspec +74 -0
  181. metadata +77 -27
  182. data/docs/Gemfile +0 -5
  183. data/docs/Gemfile.lock +0 -258
  184. data/docs/_data/nav.yml +0 -35
  185. data/docs/_includes/footer.html +0 -15
  186. data/docs/_includes/head.html +0 -19
  187. data/docs/_includes/sidebar_nav.html +0 -22
  188. data/docs/_includes/toc.html +0 -112
  189. data/docs/_layouts/default.html +0 -79
  190. data/docs/css/docs.css +0 -157
  191. data/docs/images/header.png +0 -0
  192. data/docs/installing-ruby.md +0 -28
  193. data/lib/project_types/extension/features/argo/admin.rb +0 -20
  194. data/lib/project_types/extension/features/argo/base.rb +0 -129
  195. data/lib/project_types/extension/features/argo/checkout.rb +0 -20
  196. data/lib/project_types/extension/models/type.rb +0 -81
  197. data/lib/project_types/extension/models/types/checkout_post_purchase.rb +0 -23
  198. data/lib/project_types/extension/models/types/product_subscription.rb +0 -24
  199. data/lib/project_types/node/commands/generate/billing.rb +0 -39
  200. data/lib/project_types/node/commands/generate/page.rb +0 -59
  201. data/lib/project_types/node/commands/generate/webhook.rb +0 -37
  202. data/lib/project_types/script/layers/domain/script.rb +0 -18
  203. data/lib/project_types/script/layers/infrastructure/assemblyscript_tsconfig.rb +0 -38
  204. data/lib/project_types/script/layers/infrastructure/script_repository.rb +0 -59
  205. data/lib/project_types/script/templates/ts/as-pect.config.js +0 -27
  206. data/lib/project_types/script/templates/ts/as-pect.d.ts +0 -1
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+ #--
3
+ # httpauth/authenticator.rb -- Authenticator mix-in module.
4
+ #
5
+ # Author: IPR -- Internet Programming with Ruby -- writers
6
+ # Copyright (c) 2003 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: authenticator.rb,v 1.3 2003/02/20 07:15:47 gotoyuzo Exp $
10
+
11
+ module WEBrick
12
+ module HTTPAuth
13
+
14
+ ##
15
+ # Module providing generic support for both Digest and Basic
16
+ # authentication schemes.
17
+
18
+ module Authenticator
19
+
20
+ RequestField = "Authorization" # :nodoc:
21
+ ResponseField = "WWW-Authenticate" # :nodoc:
22
+ ResponseInfoField = "Authentication-Info" # :nodoc:
23
+ AuthException = HTTPStatus::Unauthorized # :nodoc:
24
+
25
+ ##
26
+ # Method of authentication, must be overridden by the including class
27
+
28
+ AuthScheme = nil
29
+
30
+ ##
31
+ # The realm this authenticator covers
32
+
33
+ attr_reader :realm
34
+
35
+ ##
36
+ # The user database for this authenticator
37
+
38
+ attr_reader :userdb
39
+
40
+ ##
41
+ # The logger for this authenticator
42
+
43
+ attr_reader :logger
44
+
45
+ private
46
+
47
+ # :stopdoc:
48
+
49
+ ##
50
+ # Initializes the authenticator from +config+
51
+
52
+ def check_init(config)
53
+ [:UserDB, :Realm].each{|sym|
54
+ unless config[sym]
55
+ raise ArgumentError, "Argument #{sym.inspect} missing."
56
+ end
57
+ }
58
+ @realm = config[:Realm]
59
+ @userdb = config[:UserDB]
60
+ @logger = config[:Logger] || Log::new($stderr)
61
+ @reload_db = config[:AutoReloadUserDB]
62
+ @request_field = self::class::RequestField
63
+ @response_field = self::class::ResponseField
64
+ @resp_info_field = self::class::ResponseInfoField
65
+ @auth_exception = self::class::AuthException
66
+ @auth_scheme = self::class::AuthScheme
67
+ end
68
+
69
+ ##
70
+ # Ensures +req+ has credentials that can be authenticated.
71
+
72
+ def check_scheme(req)
73
+ unless credentials = req[@request_field]
74
+ error("no credentials in the request.")
75
+ return nil
76
+ end
77
+ unless match = /^#{@auth_scheme}\s+/i.match(credentials)
78
+ error("invalid scheme in %s.", credentials)
79
+ info("%s: %s", @request_field, credentials) if $DEBUG
80
+ return nil
81
+ end
82
+ return match.post_match
83
+ end
84
+
85
+ def log(meth, fmt, *args)
86
+ msg = format("%s %s: ", @auth_scheme, @realm)
87
+ msg << fmt % args
88
+ @logger.__send__(meth, msg)
89
+ end
90
+
91
+ def error(fmt, *args)
92
+ if @logger.error?
93
+ log(:error, fmt, *args)
94
+ end
95
+ end
96
+
97
+ def info(fmt, *args)
98
+ if @logger.info?
99
+ log(:info, fmt, *args)
100
+ end
101
+ end
102
+
103
+ # :startdoc:
104
+ end
105
+
106
+ ##
107
+ # Module providing generic support for both Digest and Basic
108
+ # authentication schemes for proxies.
109
+
110
+ module ProxyAuthenticator
111
+ RequestField = "Proxy-Authorization" # :nodoc:
112
+ ResponseField = "Proxy-Authenticate" # :nodoc:
113
+ InfoField = "Proxy-Authentication-Info" # :nodoc:
114
+ AuthException = HTTPStatus::ProxyAuthenticationRequired # :nodoc:
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # httpauth/basicauth.rb -- HTTP basic access authentication
4
+ #
5
+ # Author: IPR -- Internet Programming with Ruby -- writers
6
+ # Copyright (c) 2003 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: basicauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
10
+
11
+ require_relative '../config'
12
+ require_relative '../httpstatus'
13
+ require_relative 'authenticator'
14
+
15
+ module WEBrick
16
+ module HTTPAuth
17
+
18
+ ##
19
+ # Basic Authentication for WEBrick
20
+ #
21
+ # Use this class to add basic authentication to a WEBrick servlet.
22
+ #
23
+ # Here is an example of how to set up a BasicAuth:
24
+ #
25
+ # config = { :Realm => 'BasicAuth example realm' }
26
+ #
27
+ # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file', password_hash: :bcrypt
28
+ # htpasswd.set_passwd config[:Realm], 'username', 'password'
29
+ # htpasswd.flush
30
+ #
31
+ # config[:UserDB] = htpasswd
32
+ #
33
+ # basic_auth = WEBrick::HTTPAuth::BasicAuth.new config
34
+
35
+ class BasicAuth
36
+ include Authenticator
37
+
38
+ AuthScheme = "Basic" # :nodoc:
39
+
40
+ ##
41
+ # Used by UserDB to create a basic password entry
42
+
43
+ def self.make_passwd(realm, user, pass)
44
+ pass ||= ""
45
+ pass.crypt(Utils::random_string(2))
46
+ end
47
+
48
+ attr_reader :realm, :userdb, :logger
49
+
50
+ ##
51
+ # Creates a new BasicAuth instance.
52
+ #
53
+ # See WEBrick::Config::BasicAuth for default configuration entries
54
+ #
55
+ # You must supply the following configuration entries:
56
+ #
57
+ # :Realm:: The name of the realm being protected.
58
+ # :UserDB:: A database of usernames and passwords.
59
+ # A WEBrick::HTTPAuth::Htpasswd instance should be used.
60
+
61
+ def initialize(config, default=Config::BasicAuth)
62
+ check_init(config)
63
+ @config = default.dup.update(config)
64
+ end
65
+
66
+ ##
67
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
68
+ # the authentication was not correct.
69
+
70
+ def authenticate(req, res)
71
+ unless basic_credentials = check_scheme(req)
72
+ challenge(req, res)
73
+ end
74
+ userid, password = basic_credentials.unpack("m*")[0].split(":", 2)
75
+ password ||= ""
76
+ if userid.empty?
77
+ error("user id was not given.")
78
+ challenge(req, res)
79
+ end
80
+ unless encpass = @userdb.get_passwd(@realm, userid, @reload_db)
81
+ error("%s: the user is not allowed.", userid)
82
+ challenge(req, res)
83
+ end
84
+
85
+ case encpass
86
+ when /\A\$2[aby]\$/
87
+ password_matches = BCrypt::Password.new(encpass.sub(/\A\$2[aby]\$/, '$2a$')) == password
88
+ else
89
+ password_matches = password.crypt(encpass) == encpass
90
+ end
91
+
92
+ unless password_matches
93
+ error("%s: password unmatch.", userid)
94
+ challenge(req, res)
95
+ end
96
+ info("%s: authentication succeeded.", userid)
97
+ req.user = userid
98
+ end
99
+
100
+ ##
101
+ # Returns a challenge response which asks for authentication information
102
+
103
+ def challenge(req, res)
104
+ res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
105
+ raise @auth_exception
106
+ end
107
+ end
108
+
109
+ ##
110
+ # Basic authentication for proxy servers. See BasicAuth for details.
111
+
112
+ class ProxyBasicAuth < BasicAuth
113
+ include ProxyAuthenticator
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,395 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # httpauth/digestauth.rb -- HTTP digest access authentication
4
+ #
5
+ # Author: IPR -- Internet Programming with Ruby -- writers
6
+ # Copyright (c) 2003 Internet Programming with Ruby writers.
7
+ # Copyright (c) 2003 H.M.
8
+ #
9
+ # The original implementation is provided by H.M.
10
+ # URL: http://rwiki.jin.gr.jp/cgi-bin/rw-cgi.rb?cmd=view;name=
11
+ # %C7%A7%BE%DA%B5%A1%C7%BD%A4%F2%B2%FE%C2%A4%A4%B7%A4%C6%A4%DF%A4%EB
12
+ #
13
+ # $IPR: digestauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
14
+
15
+ require_relative '../config'
16
+ require_relative '../httpstatus'
17
+ require_relative 'authenticator'
18
+ require 'digest/md5'
19
+ require 'digest/sha1'
20
+
21
+ module WEBrick
22
+ module HTTPAuth
23
+
24
+ ##
25
+ # RFC 2617 Digest Access Authentication for WEBrick
26
+ #
27
+ # Use this class to add digest authentication to a WEBrick servlet.
28
+ #
29
+ # Here is an example of how to set up DigestAuth:
30
+ #
31
+ # config = { :Realm => 'DigestAuth example realm' }
32
+ #
33
+ # htdigest = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
34
+ # htdigest.set_passwd config[:Realm], 'username', 'password'
35
+ # htdigest.flush
36
+ #
37
+ # config[:UserDB] = htdigest
38
+ #
39
+ # digest_auth = WEBrick::HTTPAuth::DigestAuth.new config
40
+ #
41
+ # When using this as with a servlet be sure not to create a new DigestAuth
42
+ # object in the servlet's #initialize. By default WEBrick creates a new
43
+ # servlet instance for every request and the DigestAuth object must be
44
+ # used across requests.
45
+
46
+ class DigestAuth
47
+ include Authenticator
48
+
49
+ AuthScheme = "Digest" # :nodoc:
50
+
51
+ ##
52
+ # Struct containing the opaque portion of the digest authentication
53
+
54
+ OpaqueInfo = Struct.new(:time, :nonce, :nc) # :nodoc:
55
+
56
+ ##
57
+ # Digest authentication algorithm
58
+
59
+ attr_reader :algorithm
60
+
61
+ ##
62
+ # Quality of protection. RFC 2617 defines "auth" and "auth-int"
63
+
64
+ attr_reader :qop
65
+
66
+ ##
67
+ # Used by UserDB to create a digest password entry
68
+
69
+ def self.make_passwd(realm, user, pass)
70
+ pass ||= ""
71
+ Digest::MD5::hexdigest([user, realm, pass].join(":"))
72
+ end
73
+
74
+ ##
75
+ # Creates a new DigestAuth instance. Be sure to use the same DigestAuth
76
+ # instance for multiple requests as it saves state between requests in
77
+ # order to perform authentication.
78
+ #
79
+ # See WEBrick::Config::DigestAuth for default configuration entries
80
+ #
81
+ # You must supply the following configuration entries:
82
+ #
83
+ # :Realm:: The name of the realm being protected.
84
+ # :UserDB:: A database of usernames and passwords.
85
+ # A WEBrick::HTTPAuth::Htdigest instance should be used.
86
+
87
+ def initialize(config, default=Config::DigestAuth)
88
+ check_init(config)
89
+ @config = default.dup.update(config)
90
+ @algorithm = @config[:Algorithm]
91
+ @domain = @config[:Domain]
92
+ @qop = @config[:Qop]
93
+ @use_opaque = @config[:UseOpaque]
94
+ @use_next_nonce = @config[:UseNextNonce]
95
+ @check_nc = @config[:CheckNc]
96
+ @use_auth_info_header = @config[:UseAuthenticationInfoHeader]
97
+ @nonce_expire_period = @config[:NonceExpirePeriod]
98
+ @nonce_expire_delta = @config[:NonceExpireDelta]
99
+ @internet_explorer_hack = @config[:InternetExplorerHack]
100
+
101
+ case @algorithm
102
+ when 'MD5','MD5-sess'
103
+ @h = Digest::MD5
104
+ when 'SHA1','SHA1-sess' # it is a bonus feature :-)
105
+ @h = Digest::SHA1
106
+ else
107
+ msg = format('Algorithm "%s" is not supported.', @algorithm)
108
+ raise ArgumentError.new(msg)
109
+ end
110
+
111
+ @instance_key = hexdigest(self.__id__, Time.now.to_i, Process.pid)
112
+ @opaques = {}
113
+ @last_nonce_expire = Time.now
114
+ @mutex = Thread::Mutex.new
115
+ end
116
+
117
+ ##
118
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
119
+ # the authentication was not correct.
120
+
121
+ def authenticate(req, res)
122
+ unless result = @mutex.synchronize{ _authenticate(req, res) }
123
+ challenge(req, res)
124
+ end
125
+ if result == :nonce_is_stale
126
+ challenge(req, res, true)
127
+ end
128
+ return true
129
+ end
130
+
131
+ ##
132
+ # Returns a challenge response which asks for authentication information
133
+
134
+ def challenge(req, res, stale=false)
135
+ nonce = generate_next_nonce(req)
136
+ if @use_opaque
137
+ opaque = generate_opaque(req)
138
+ @opaques[opaque].nonce = nonce
139
+ end
140
+
141
+ param = Hash.new
142
+ param["realm"] = HTTPUtils::quote(@realm)
143
+ param["domain"] = HTTPUtils::quote(@domain.to_a.join(" ")) if @domain
144
+ param["nonce"] = HTTPUtils::quote(nonce)
145
+ param["opaque"] = HTTPUtils::quote(opaque) if opaque
146
+ param["stale"] = stale.to_s
147
+ param["algorithm"] = @algorithm
148
+ param["qop"] = HTTPUtils::quote(@qop.to_a.join(",")) if @qop
149
+
150
+ res[@response_field] =
151
+ "#{@auth_scheme} " + param.map{|k,v| "#{k}=#{v}" }.join(", ")
152
+ info("%s: %s", @response_field, res[@response_field]) if $DEBUG
153
+ raise @auth_exception
154
+ end
155
+
156
+ private
157
+
158
+ # :stopdoc:
159
+
160
+ MustParams = ['username','realm','nonce','uri','response']
161
+ MustParamsAuth = ['cnonce','nc']
162
+
163
+ def _authenticate(req, res)
164
+ unless digest_credentials = check_scheme(req)
165
+ return false
166
+ end
167
+
168
+ auth_req = split_param_value(digest_credentials)
169
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
170
+ req_params = MustParams + MustParamsAuth
171
+ else
172
+ req_params = MustParams
173
+ end
174
+ req_params.each{|key|
175
+ unless auth_req.has_key?(key)
176
+ error('%s: parameter missing. "%s"', auth_req['username'], key)
177
+ raise HTTPStatus::BadRequest
178
+ end
179
+ }
180
+
181
+ if !check_uri(req, auth_req)
182
+ raise HTTPStatus::BadRequest
183
+ end
184
+
185
+ if auth_req['realm'] != @realm
186
+ error('%s: realm unmatch. "%s" for "%s"',
187
+ auth_req['username'], auth_req['realm'], @realm)
188
+ return false
189
+ end
190
+
191
+ auth_req['algorithm'] ||= 'MD5'
192
+ if auth_req['algorithm'].upcase != @algorithm.upcase
193
+ error('%s: algorithm unmatch. "%s" for "%s"',
194
+ auth_req['username'], auth_req['algorithm'], @algorithm)
195
+ return false
196
+ end
197
+
198
+ if (@qop.nil? && auth_req.has_key?('qop')) ||
199
+ (@qop && (! @qop.member?(auth_req['qop'])))
200
+ error('%s: the qop is not allowed. "%s"',
201
+ auth_req['username'], auth_req['qop'])
202
+ return false
203
+ end
204
+
205
+ password = @userdb.get_passwd(@realm, auth_req['username'], @reload_db)
206
+ unless password
207
+ error('%s: the user is not allowed.', auth_req['username'])
208
+ return false
209
+ end
210
+
211
+ nonce_is_invalid = false
212
+ if @use_opaque
213
+ info("@opaque = %s", @opaque.inspect) if $DEBUG
214
+ if !(opaque = auth_req['opaque'])
215
+ error('%s: opaque is not given.', auth_req['username'])
216
+ nonce_is_invalid = true
217
+ elsif !(opaque_struct = @opaques[opaque])
218
+ error('%s: invalid opaque is given.', auth_req['username'])
219
+ nonce_is_invalid = true
220
+ elsif !check_opaque(opaque_struct, req, auth_req)
221
+ @opaques.delete(auth_req['opaque'])
222
+ nonce_is_invalid = true
223
+ end
224
+ elsif !check_nonce(req, auth_req)
225
+ nonce_is_invalid = true
226
+ end
227
+
228
+ if /-sess$/i =~ auth_req['algorithm']
229
+ ha1 = hexdigest(password, auth_req['nonce'], auth_req['cnonce'])
230
+ else
231
+ ha1 = password
232
+ end
233
+
234
+ if auth_req['qop'] == "auth" || auth_req['qop'] == nil
235
+ ha2 = hexdigest(req.request_method, auth_req['uri'])
236
+ ha2_res = hexdigest("", auth_req['uri'])
237
+ elsif auth_req['qop'] == "auth-int"
238
+ body_digest = @h.new
239
+ req.body { |chunk| body_digest.update(chunk) }
240
+ body_digest = body_digest.hexdigest
241
+ ha2 = hexdigest(req.request_method, auth_req['uri'], body_digest)
242
+ ha2_res = hexdigest("", auth_req['uri'], body_digest)
243
+ end
244
+
245
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
246
+ param2 = ['nonce', 'nc', 'cnonce', 'qop'].map{|key|
247
+ auth_req[key]
248
+ }.join(':')
249
+ digest = hexdigest(ha1, param2, ha2)
250
+ digest_res = hexdigest(ha1, param2, ha2_res)
251
+ else
252
+ digest = hexdigest(ha1, auth_req['nonce'], ha2)
253
+ digest_res = hexdigest(ha1, auth_req['nonce'], ha2_res)
254
+ end
255
+
256
+ if digest != auth_req['response']
257
+ error("%s: digest unmatch.", auth_req['username'])
258
+ return false
259
+ elsif nonce_is_invalid
260
+ error('%s: digest is valid, but nonce is not valid.',
261
+ auth_req['username'])
262
+ return :nonce_is_stale
263
+ elsif @use_auth_info_header
264
+ auth_info = {
265
+ 'nextnonce' => generate_next_nonce(req),
266
+ 'rspauth' => digest_res
267
+ }
268
+ if @use_opaque
269
+ opaque_struct.time = req.request_time
270
+ opaque_struct.nonce = auth_info['nextnonce']
271
+ opaque_struct.nc = "%08x" % (auth_req['nc'].hex + 1)
272
+ end
273
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
274
+ ['qop','cnonce','nc'].each{|key|
275
+ auth_info[key] = auth_req[key]
276
+ }
277
+ end
278
+ res[@resp_info_field] = auth_info.keys.map{|key|
279
+ if key == 'nc'
280
+ key + '=' + auth_info[key]
281
+ else
282
+ key + "=" + HTTPUtils::quote(auth_info[key])
283
+ end
284
+ }.join(', ')
285
+ end
286
+ info('%s: authentication succeeded.', auth_req['username'])
287
+ req.user = auth_req['username']
288
+ return true
289
+ end
290
+
291
+ def split_param_value(string)
292
+ ret = {}
293
+ string.scan(/\G\s*([\w\-.*%!]+)=\s*(?:\"((?>\\.|[^\"])*)\"|([^,\"]*))\s*,?/) do
294
+ ret[$1] = $3 || $2.gsub(/\\(.)/, "\\1")
295
+ end
296
+ ret
297
+ end
298
+
299
+ def generate_next_nonce(req)
300
+ now = "%012d" % req.request_time.to_i
301
+ pk = hexdigest(now, @instance_key)[0,32]
302
+ nonce = [now + ":" + pk].pack("m0") # it has 60 length of chars.
303
+ nonce
304
+ end
305
+
306
+ def check_nonce(req, auth_req)
307
+ username = auth_req['username']
308
+ nonce = auth_req['nonce']
309
+
310
+ pub_time, pk = nonce.unpack("m*")[0].split(":", 2)
311
+ if (!pub_time || !pk)
312
+ error("%s: empty nonce is given", username)
313
+ return false
314
+ elsif (hexdigest(pub_time, @instance_key)[0,32] != pk)
315
+ error("%s: invalid private-key: %s for %s",
316
+ username, hexdigest(pub_time, @instance_key)[0,32], pk)
317
+ return false
318
+ end
319
+
320
+ diff_time = req.request_time.to_i - pub_time.to_i
321
+ if (diff_time < 0)
322
+ error("%s: difference of time-stamp is negative.", username)
323
+ return false
324
+ elsif diff_time > @nonce_expire_period
325
+ error("%s: nonce is expired.", username)
326
+ return false
327
+ end
328
+
329
+ return true
330
+ end
331
+
332
+ def generate_opaque(req)
333
+ @mutex.synchronize{
334
+ now = req.request_time
335
+ if now - @last_nonce_expire > @nonce_expire_delta
336
+ @opaques.delete_if{|key,val|
337
+ (now - val.time) > @nonce_expire_period
338
+ }
339
+ @last_nonce_expire = now
340
+ end
341
+ begin
342
+ opaque = Utils::random_string(16)
343
+ end while @opaques[opaque]
344
+ @opaques[opaque] = OpaqueInfo.new(now, nil, '00000001')
345
+ opaque
346
+ }
347
+ end
348
+
349
+ def check_opaque(opaque_struct, req, auth_req)
350
+ if (@use_next_nonce && auth_req['nonce'] != opaque_struct.nonce)
351
+ error('%s: nonce unmatched. "%s" for "%s"',
352
+ auth_req['username'], auth_req['nonce'], opaque_struct.nonce)
353
+ return false
354
+ elsif !check_nonce(req, auth_req)
355
+ return false
356
+ end
357
+ if (@check_nc && auth_req['nc'] != opaque_struct.nc)
358
+ error('%s: nc unmatched."%s" for "%s"',
359
+ auth_req['username'], auth_req['nc'], opaque_struct.nc)
360
+ return false
361
+ end
362
+ true
363
+ end
364
+
365
+ def check_uri(req, auth_req)
366
+ uri = auth_req['uri']
367
+ if uri != req.request_uri.to_s && uri != req.unparsed_uri &&
368
+ (@internet_explorer_hack && uri != req.path)
369
+ error('%s: uri unmatch. "%s" for "%s"', auth_req['username'],
370
+ auth_req['uri'], req.request_uri.to_s)
371
+ return false
372
+ end
373
+ true
374
+ end
375
+
376
+ def hexdigest(*args)
377
+ @h.hexdigest(args.join(":"))
378
+ end
379
+
380
+ # :startdoc:
381
+ end
382
+
383
+ ##
384
+ # Digest authentication for proxy servers. See DigestAuth for details.
385
+
386
+ class ProxyDigestAuth < DigestAuth
387
+ include ProxyAuthenticator
388
+
389
+ private
390
+ def check_uri(req, auth_req) # :nodoc:
391
+ return true
392
+ end
393
+ end
394
+ end
395
+ end