tenter 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2cec3086122a566c922ffaeeda6e9d086c3dc78cd4900666e6e0ae2edefb15a3
4
- data.tar.gz: 9dacc38cf020d66efa977acdbf2dfab90527b926445b5f7aa5e1e57206a7c0b0
3
+ metadata.gz: e691f0744f22ab97bd7793c42c570328202deffe78aae13965ca528b1376c2ae
4
+ data.tar.gz: 1d79b962a619c625d8b812951087d913a3739ff7d8ca0d4e20f4b101ad820059
5
5
  SHA512:
6
- metadata.gz: b535883fe1cd070d1f8706a895c043db077950bb8e2c0d622e310e026831feed390a79980e5b0d39510c1abbed8f0f3b1072b8c4df0d92d390200724d2396193
7
- data.tar.gz: 660effd30f91a6d47745ee803d73b5f0082183c49268aca2bf7fa3cabb52dfd03bef29d683c282d12081ce6c3362ca80a3e6e4b2be319184972f69b616344b0e
6
+ metadata.gz: 8e5516a38863c5885008b5752c86ecd7f5c6ba300b266b4867240c92dfdafb47804cd38ccd6215f2367ec747051bf6c4baca7775f3a8e9550713c5bc1943e587
7
+ data.tar.gz: 7180c0c1bd2a01091089230f81b506b9d931543bf66ca7708213c06b888ea8ebf6125fab512beef261276b5096e0fcd98c58b5f080bc5d889ce75af805f32f0f
@@ -5,7 +5,68 @@ require "tenter/hooks"
5
5
  require "tenter/utils"
6
6
  require "tenter/version"
7
7
 
8
+ # A web app that provides webhooks for GitHub
9
+ #
10
+ # Tenter is a Sinatra-based web application that provides webhooks for use by
11
+ # GitHub. It is intended to be used as a gem in a Rack app.
12
+ #
13
+ # At its simplest, a user could write a `config.ru` file consisting of:
14
+ #
15
+ # require "tenter"
16
+ #
17
+ # run Tenter::Hooks
18
+ #
19
+ # Tenter comes with a series of sane defaults. A version of Tenter that uses
20
+ # these will expose endpoints in the form `/run/<action>/in/<dirname>/`. HTTP
21
+ # POST requests sent to such an endpoint will, if authenticated, result in
22
+ # Tenter executing the file on your the server's local file system at
23
+ # `/<doc_root>/<dirname>/commands/<action>`.
24
+ #
25
+ # As astute readers will have observed, this is a potentially massive security
26
+ # vulnerability. I am nevertheless able to sleep at night because Tenter
27
+ # will only execute a file matching the action in a given directory if:
28
+ #
29
+ # 1. the POST request includes an `HTTP_X_HUB_SIGNATURE` header; and
30
+ # 2. the value of this header matches the result of cryptographically
31
+ # signing the body of the HTTP request with a key defined by the user.
32
+ #
33
+ # By default, the key defined by the user is located in
34
+ # `/<doc_root>/<dirname>/hooks.yaml`.
35
+ #
36
+ # A user can override the default settings for their instance of Tenter by
37
+ # defining one or more of the following:
38
+ #
39
+ # - `:doc_root` (default: `"/var/www"`): The root directory in which each
40
+ # exposed directory will be located. It's recommended to specify this as an
41
+ # absolute path.
42
+ #
43
+ # - `:config_filename` (default: `"hooks.yaml"`): The filename of the
44
+ # configuration file in each exposed directory.
45
+ #
46
+ # - `:command_dir` (default: `"commands"`): The name of the subdirectory
47
+ # in which the files to execute are located.
48
+ #
49
+ # - `:log_file` (default `"log/commands.log"`): The path to the log file in each
50
+ # exposed directory where output from your commands will be logged. You can
51
+ # set this to `nil`to disable logging.
52
+ #
53
+ # The easiest way to do that is in the `config.ru` file:
54
+ #
55
+ # require "tenter"
56
+ #
57
+ # Tenter.settings = { log_file: nil } # disable logging
58
+ #
59
+ # run Tenter::Hooks
60
+ #
61
+ # @since 0.1.1
62
+ # @see https://github.com/pyrmont/tenter
8
63
  module Tenter
64
+
65
+ # Returns the default settings
66
+ #
67
+ # @return [Hash] the default settings
68
+ #
69
+ # @since 0.1.1
9
70
  def self.defaults
10
71
  { doc_root: "/var/www/",
11
72
  config_filename: "hooks.yaml",
@@ -14,15 +75,28 @@ module Tenter
14
75
  timestamp: true }
15
76
  end
16
77
 
78
+ # Resets Tenter's settings to their defaults
79
+ #
80
+ # @since 0.1.1
17
81
  def self.reset
18
82
  @settings = self.defaults
19
83
  end
20
84
 
85
+ # Updates the provided settings
86
+ #
87
+ # @param opts [Hash] the keys and values to update
88
+ #
89
+ # @since 0.1.1
21
90
  def self.settings=(opts = {})
22
91
  @settings = self.settings.merge opts
23
92
  end
24
93
 
94
+ # Returns the settings for this instance
95
+ #
96
+ # @return [Hash] the settings for this instance
97
+ #
98
+ # @since 0.1.1
25
99
  def self.settings
26
- @settings ||= self.defaults
100
+ @settings ||= self.defaults
27
101
  end
28
102
  end
@@ -3,14 +3,28 @@
3
3
  require "openssl"
4
4
 
5
5
  module Tenter
6
+
7
+ # Sinatra helpers for Tenter
8
+ #
9
+ # These helpers provide idiomatic methods for use in Sinatra routes. This
10
+ # module is intended to be called by passing it to the `Sinatra::Base.helpers`
11
+ # method.
12
+ #
13
+ # @since 0.1.1
14
+ # @api private
6
15
  module Helpers
16
+
17
+ # Authenticates the request
18
+ #
19
+ # @since 0.1.1
20
+ # @api private
7
21
  def authenticate
8
22
  msg = "X-Hub-Signature header not set"
9
23
  halt 400, msg unless request.env['HTTP_X_HUB_SIGNATURE']
10
24
 
11
25
  msg = "X-Hub-Signature header did not match"
12
26
  halt 403, msg unless Tenter::Utils.dir_exists? params[:site_dir]
13
-
27
+
14
28
  secret = Tenter::Utils.secret params[:site_dir]
15
29
 
16
30
  request_sig = request.env['HTTP_X_HUB_SIGNATURE']
@@ -19,11 +33,15 @@ module Tenter
19
33
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'),
20
34
  secret,
21
35
  request_body)
22
-
36
+
23
37
  msg = "X-Hub-Signature header did not match"
24
38
  halt 403, msg unless Rack::Utils.secure_compare computed_sig, request_sig
25
39
  end
26
40
 
41
+ # Executes the command
42
+ #
43
+ # @since 0.1.1
44
+ # @api private
27
45
  def initiate
28
46
  command = Tenter::Utils.command params[:command_name], params[:site_dir]
29
47
 
@@ -34,14 +52,19 @@ module Tenter
34
52
  msg = ts + "Initiating: #{command["path"]}\n"
35
53
  Tenter::Utils.append_to_log command["log"], msg
36
54
 
37
- pid = if defined?(Bundler) && Bundler.respond_to?(:with_clean_env)
38
- Bundler.with_clean_env { command["proc"].call }
55
+ pid = if defined?(Bundler) && Bundler.respond_to?(:with_original_env)
56
+ Bundler.with_original_env { command["proc"].call }
39
57
  else
40
58
  command["proc"].call
41
59
  end
42
60
  (ENV["APP_ENV"] != "test") ? Process.detach(pid) : Process.wait(pid)
43
61
  end
44
62
 
63
+ # Generates the response's HTTP header status and body
64
+ #
65
+ # @param message [Symbol] the type of response
66
+ # @since 0.1.1
67
+ # @api private
45
68
  def notify(message)
46
69
  case message
47
70
  when :initiated
@@ -3,6 +3,13 @@
3
3
  require "sinatra/base"
4
4
 
5
5
  module Tenter
6
+
7
+ # The Sinatra application
8
+ #
9
+ # This sets out the routes for the Sinatra application.
10
+ #
11
+ # @since 0.1.1
12
+ # @api private
6
13
  class Hooks < Sinatra::Base
7
14
  helpers Tenter::Helpers
8
15
 
@@ -3,7 +3,22 @@
3
3
  require "yaml"
4
4
 
5
5
  module Tenter
6
+
7
+ # Utility functions for use by Tenter
8
+ #
9
+ # @since 0.1.1
10
+ # @api private
6
11
  module Utils
12
+
13
+ # Loads configuration data from a YAML file for a given directory
14
+ #
15
+ # @note The basename of the YAML file is specified in Tenter's configuration
16
+ # settings.
17
+ #
18
+ # @param site_dir [String] the directory containing the YAML file
19
+ #
20
+ # @since 0.1.1
21
+ # @api private
7
22
  def self.config(site_dir)
8
23
  @config ||= {}
9
24
  @config[site_dir] ||=
@@ -12,14 +27,40 @@ module Tenter
12
27
  Tenter.settings[:config_filename]))
13
28
  end
14
29
 
30
+ # Checks if the directory exists
31
+ #
32
+ # @note The directory provided must be given relative to the document root.
33
+ #
34
+ # @param site_dir [String] the directory to check
35
+ #
36
+ # @since 0.1.1
37
+ # @api private
15
38
  def self.dir_exists?(site_dir)
16
39
  File.directory? File.join(Tenter.settings[:doc_root], site_dir)
17
40
  end
18
41
 
42
+ # Returns the secret value for a particular directory
43
+ #
44
+ # @param site_dir [String} the directory
45
+ #
46
+ # @since 0.1.1
47
+ # @api private
19
48
  def self.secret(site_dir)
20
49
  self.config(site_dir).fetch("secret", nil)
21
50
  end
22
51
 
52
+ # Returns the details of the command to execute
53
+ #
54
+ # @note The directory provided must be given relative to the document root.
55
+ #
56
+ # @param command_name [String] the name of the file representing the
57
+ # command
58
+ # @param site_dir [String} the directory containing the command
59
+ #
60
+ # @return [Hash] the details of the command to execute
61
+ #
62
+ # @since 0.1.1
63
+ # @api private
23
64
  def self.command(command_name, site_dir)
24
65
  site_path = File.join(Tenter.settings[:doc_root], site_dir)
25
66
  command = {}
@@ -43,6 +84,13 @@ module Tenter
43
84
  return command
44
85
  end
45
86
 
87
+ # Appends a statement to the log
88
+ #
89
+ # @param log [String] the file representing the log
90
+ # @param statement [String] the statement to append to the log
91
+ #
92
+ # @since 0.1.0
93
+ # @api private
46
94
  def self.append_to_log(log, statement)
47
95
  File.write(log, statement, mode: "a")
48
96
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tenter
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -3,28 +3,30 @@
3
3
  require "./lib/tenter/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = "tenter"
7
- s.version = Tenter::VERSION
8
- s.authors = ["Michael Camilleri"]
9
- s.email = ["mike@inqk.net"]
10
- s.summary = "A web app for running user-defined commands"
6
+ s.name = "tenter"
7
+ s.version = Tenter::VERSION
8
+ s.authors = ["Michael Camilleri"]
9
+ s.email = ["mike@inqk.net"]
10
+ s.summary = "A web app for running user-defined commands"
11
11
  s.description = <<-desc.strip.gsub(/\s+/, " ")
12
12
  Tenter is a Sinatra-based application that provides webhooks for use by
13
13
  GitHub.
14
14
  desc
15
- s.homepage = "https://github.com/pyrmont/tenter/"
16
- s.licenses = "Unlicense"
17
- s.required_ruby_version = ">= 2.5"
15
+ s.homepage = "https://github.com/pyrmont/tenter/"
16
+ s.licenses = "Unlicense"
17
+ s.files = Dir["Gemfile", "LICENSE", "tenter.gemspec", "lib/tenter.rb",
18
+ "lib/**/*.rb"]
18
19
 
19
- s.files = Dir["Gemfile", "LICENSE", "tenter.gemspec", "lib/tenter.rb",
20
- "lib/**/*.rb"]
21
- s.require_paths = ["lib"]
22
-
20
+ s.required_ruby_version = ">= 2.5"
21
+ s.require_paths = ["lib"]
23
22
  s.metadata["allowed_push_host"] = "https://rubygems.org"
24
23
 
25
24
  s.add_runtime_dependency "sinatra", "~> 2.0"
25
+ s.add_runtime_dependency "thin", "~> 1.0"
26
26
 
27
- s.add_development_dependency "minitest", "~> 5.11"
28
- s.add_development_dependency "rack-test", "~> 1.1"
29
- s.add_development_dependency "rake", "~> 12.3"
27
+ s.add_development_dependency "minitest", "~> 5.0"
28
+ s.add_development_dependency "rack-test", "~> 1.0"
29
+ s.add_development_dependency "rake", "~> 13.0"
30
+ s.add_development_dependency "redcarpet", "~> 3.0"
31
+ s.add_development_dependency "yard", "~> 0.0"
30
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Camilleri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-08 00:00:00.000000000 Z
11
+ date: 2020-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -24,48 +24,90 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thin
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: minitest
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '5.11'
47
+ version: '5.0'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '5.11'
54
+ version: '5.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rack-test
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '1.1'
61
+ version: '1.0'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '1.1'
68
+ version: '1.0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '12.3'
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: redcarpet
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.0'
62
104
  type: :development
63
105
  prerelease: false
64
106
  version_requirements: !ruby/object:Gem::Requirement
65
107
  requirements:
66
108
  - - "~>"
67
109
  - !ruby/object:Gem::Version
68
- version: '12.3'
110
+ version: '0.0'
69
111
  description: Tenter is a Sinatra-based application that provides webhooks for use
70
112
  by GitHub.
71
113
  email:
@@ -102,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
144
  - !ruby/object:Gem::Version
103
145
  version: '0'
104
146
  requirements: []
105
- rubygems_version: 3.0.3
147
+ rubygems_version: 3.1.2
106
148
  signing_key:
107
149
  specification_version: 4
108
150
  summary: A web app for running user-defined commands