boxen-halyard 2.8.0.akerl9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ require "boxen/hook"
2
+ require "json"
3
+ require "net/http"
4
+
5
+ module Boxen
6
+ class Hook
7
+ class Web < Hook
8
+ def perform?
9
+ enabled?
10
+ end
11
+
12
+ private
13
+ def call
14
+ payload = {
15
+ :login => config.user,
16
+ :sha => checkout.sha,
17
+ :status => result.success? ? 'success' : 'failure',
18
+ :time => "#{Time.now.utc.to_i}"
19
+ }
20
+
21
+ post_web_hook payload
22
+ end
23
+
24
+ def post_web_hook(payload)
25
+ headers = { 'Content-Type' => 'application/json' }
26
+
27
+ uri = URI.parse(URI.escape(ENV['BOXEN_WEB_HOOK_URL']))
28
+
29
+ user, pass, host, port, path = \
30
+ uri.user, uri.pass, uri.host, uri.port, uri.path
31
+
32
+ request = Net::HTTP::Post.new(path, initheader = headers)
33
+
34
+ if uri.scheme =~ /https/
35
+ http.use_ssl = true
36
+ end
37
+
38
+ if user && pass
39
+ request.basic_auth user, pass
40
+ end
41
+
42
+ request.body = payload.to_json
43
+
44
+ response = Net::HTTP.new(host, port).start do |http|
45
+ http.request(request)
46
+ end
47
+ end
48
+
49
+ def required_environment_variables
50
+ ['BOXEN_WEB_HOOK_URL']
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ Boxen::Hook.register Boxen::Hook::Web
@@ -0,0 +1,63 @@
1
+ require "shellwords"
2
+
3
+ module Boxen
4
+ class Keychain
5
+
6
+ # The keychain proxy we use to provide isolation and a friendly
7
+ # message in security prompts.
8
+
9
+ HELPER = File.expand_path "../../../script/Boxen", __FILE__
10
+
11
+ # The service name to use when loading/saving passwords.
12
+
13
+ PASSWORD_SERVICE = "GitHub Password"
14
+
15
+ # The service name to use when loading/saving API keys.
16
+
17
+ TOKEN_SERVICE = "GitHub API Token"
18
+
19
+ def initialize(login, local_user)
20
+ @login = login
21
+ @local_user = local_user
22
+ # Clear the password. We're storing tokens now.
23
+ set PASSWORD_SERVICE, ""
24
+ end
25
+
26
+ def token
27
+ get TOKEN_SERVICE
28
+ end
29
+
30
+ def token=(token)
31
+ set TOKEN_SERVICE, token
32
+ end
33
+
34
+ protected
35
+
36
+ attr_reader :login
37
+
38
+ def get(service)
39
+ cmd = shellescape(
40
+ '/usr/bin/sudo', '-u', @local_user, HELPER, service, login
41
+ )
42
+
43
+ result = `#{cmd}`.strip
44
+ $?.success? ? result : nil
45
+ end
46
+
47
+ def set(service, token)
48
+ cmd = shellescape(
49
+ '/usr/bin/sudo', '-u', @local_user, HELPER, service, login, token
50
+ )
51
+
52
+ unless system *cmd
53
+ raise Boxen::Error, "Can't save #{service} in the keychain."
54
+ end
55
+
56
+ token
57
+ end
58
+
59
+ def shellescape(*args)
60
+ args.map { |s| Shellwords.shellescape s }.join " "
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,13 @@
1
+ require "boxen/check"
2
+
3
+ module Boxen
4
+
5
+ # The superclass for postflight checks.
6
+
7
+ class Postflight < Boxen::Check
8
+
9
+ # Load all available postflight checks.
10
+
11
+ register File.expand_path("../postflight", __FILE__)
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require "boxen/postflight"
2
+ require "boxen/util"
3
+
4
+ # Checks to see if the basic environment is loaded.
5
+
6
+ class Boxen::Postflight::Active < Boxen::Postflight
7
+ def ok?
8
+ Boxen::Util.active?
9
+ end
10
+
11
+ def run
12
+ warn "You haven't loaded Boxen's environment yet!",
13
+ "To permanently fix this, source #{config.envfile} at the end",
14
+ "of your shell's startup file."
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ require "boxen/postflight"
2
+
3
+ class Boxen::Postflight::Env < Boxen::Postflight
4
+
5
+ # Calculate an MD5 checksum for the current environment.
6
+
7
+ def self.checksum
8
+
9
+ # We can't get this from config 'cause it's static (gotta happen
10
+ # on load), and BOXEN_HOME might not be set.
11
+
12
+ home = ENV["BOXEN_HOME"] || "/opt/boxen"
13
+ return unless File.file? "#{home}/env.sh"
14
+
15
+ `find #{home}/env* -type f 2>&1 | sort | xargs /sbin/md5 | /sbin/md5 -q`.strip
16
+ end
17
+
18
+ # The checksum when this file was loaded.
19
+
20
+ CHECKSUM = self.checksum
21
+
22
+ def ok?
23
+ self.class.checksum == CHECKSUM
24
+ end
25
+
26
+ def run
27
+ warn "Run source #{config.envfile} or restart your shell for new stuff!"
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ require "boxen/check"
2
+
3
+ module Boxen
4
+
5
+ # The superclass for preflight checks.
6
+
7
+ class Preflight < Boxen::Check
8
+
9
+ # Load all available preflight checks.
10
+
11
+ register File.expand_path("../preflight", __FILE__)
12
+ end
13
+ end
@@ -0,0 +1,149 @@
1
+ require "boxen/preflight"
2
+ require "highline"
3
+ require "octokit"
4
+ require "digest"
5
+ require "socket"
6
+
7
+ # HACK: Unless this is `false`, HighLine has some really bizarre
8
+ # problems with empty/expended streams at bizarre intervals.
9
+
10
+ HighLine.track_eof = false
11
+
12
+ class Boxen::Preflight::Creds < Boxen::Preflight
13
+ attr :otp
14
+ attr :password
15
+
16
+ def ok?
17
+ if config.token && config.api.user
18
+ # There was a period of time when login wasn't geting set on first run.
19
+ # This should correct that.
20
+ config.login = config.api.user.login
21
+ true
22
+ end
23
+ rescue
24
+ nil
25
+ end
26
+
27
+ def tmp_api
28
+ @tmp_api ||= Octokit::Client.new :login => config.login, :password => password, :auto_paginate => true
29
+ end
30
+
31
+ def headers
32
+ otp.nil? ? {} : {"X-GitHub-OTP" => otp}
33
+ end
34
+
35
+ def get_otp
36
+ console = HighLine.new
37
+
38
+ # junk API call to send OTP until we implement PUT
39
+ tmp_api.create_authorization rescue nil
40
+
41
+ @otp = console.ask "One time password (via SMS or device):" do |q|
42
+ q.echo = '*'
43
+ end
44
+ end
45
+
46
+ # Attempt to use the username+password to get a list of the user's OAuth
47
+ # authorizations from the API. If it fails because of 2FA, ask the user for
48
+ # her OTP and try again.
49
+ #
50
+ # Returns a list of authorizations
51
+ def get_tokens
52
+ begin
53
+ tmp_api.authorizations(:headers => headers)
54
+ rescue Octokit::Unauthorized
55
+ abort "Sorry, I can't auth you on GitHub.",
56
+ "Please check your credentials and teams and give it another try."
57
+ rescue Octokit::OneTimePasswordRequired
58
+ puts
59
+ if otp.nil?
60
+ warn "It looks like you have two-factor auth enabled."
61
+ else
62
+ warn "That one time password didn't work. Let's try again."
63
+ end
64
+ get_otp
65
+ get_tokens
66
+ end
67
+ end
68
+
69
+ def run
70
+ fetch_login_and_password
71
+ tokens = get_tokens
72
+
73
+ # Boxen now supports the updated GitHub Authorizations API by using a unique
74
+ # `fingerprint` for each Boxen installation for a user. We delete any older
75
+ # authorization that does not make use of `fingerprint` so that the "legacy"
76
+ # authorization doesn't persist in the user's list of personal access
77
+ # tokens.
78
+ legacy_auth = tokens.detect { |a| a.note == "Boxen" && a.fingerprint == nil }
79
+ tmp_api.delete_authorization(legacy_auth.id, :headers => headers) if legacy_auth
80
+
81
+ # The updated GitHub authorizations API, in order to improve security, no
82
+ # longer returns a plaintext `token` for existing authorizations. So, if an
83
+ # authorization already exists for this machine we need to first delete it
84
+ # so that we can create a new one.
85
+ auth = tokens.detect { |a| a.note == note && a.fingerprint == fingerprint }
86
+ tmp_api.delete_authorization(auth.id, :headers => headers) if auth
87
+
88
+ auth = tmp_api.create_authorization(
89
+ :note => note,
90
+ :scopes => %w(repo user admin:public_key),
91
+ :fingerprint => fingerprint,
92
+ :headers => headers
93
+ )
94
+
95
+ config.token = auth.token
96
+
97
+ unless ok?
98
+ puts
99
+ abort "Something went terribly wrong.",
100
+ "I was able to get your OAuth token, but was unable to use it."
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def fetch_login_and_password
107
+ console = HighLine.new
108
+
109
+ config.login = fetch_from_env("login") || console.ask("GitHub login: ") do |q|
110
+ q.default = config.login || config.user
111
+ q.validate = /\A[^@]+\Z/
112
+ end
113
+
114
+ @password = fetch_from_env("password") || console.ask("GitHub password: ") do |q|
115
+ q.echo = "*"
116
+ end
117
+ end
118
+
119
+ def fetch_from_env(thing)
120
+ key = "BOXEN_GITHUB_#{thing.upcase}"
121
+ return unless found = ENV[key]
122
+ warn "Oh, looks like you've provided your #{thing} as environmental variable..."
123
+ found
124
+ end
125
+
126
+ def fingerprint
127
+ @fingerprint ||= begin
128
+ # See Apple technical note TN1103, "Uniquely Identifying a Macintosh
129
+ # Computer."
130
+ serial_number_match_data = IO.popen(
131
+ ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"]
132
+ ).read.match(/"IOPlatformSerialNumber" = "([[:alnum:]]+)"/)
133
+ if serial_number_match_data
134
+ # The fingerprint must be unique across all personal access tokens for a
135
+ # given user. We prefix the serial number with the application name to
136
+ # differentiate between any other personal access token that uses the
137
+ # Mac serial number for the fingerprint.
138
+ Digest::SHA256.hexdigest("Boxen: #{serial_number_match_data[1]}")
139
+ else
140
+ abort "Sorry, I was unable to obtain your Mac's serial number.",
141
+ "Boxen requires access to your Mac's serial number in order to generate a unique GitHub personal access token."
142
+ end
143
+ end
144
+ end
145
+
146
+ def note
147
+ @note ||= "Boxen: #{Socket.gethostname}"
148
+ end
149
+ end
@@ -0,0 +1,32 @@
1
+ require "boxen/preflight"
2
+ require "boxen/util"
3
+
4
+ class Boxen::Preflight::Directories < Boxen::Preflight
5
+ def ok?
6
+ homedir_directory_exists? &&
7
+ homedir_owner == config.user &&
8
+ homedir_group == 'staff'
9
+ end
10
+
11
+ def run
12
+ Boxen::Util.sudo("/bin/mkdir", "-p", config.homedir) &&
13
+ Boxen::Util.sudo("/usr/sbin/chown", "#{config.user}:staff", config.homedir)
14
+ end
15
+
16
+ private
17
+ def homedir_directory_exists?
18
+ File.directory?(config.homedir)
19
+ end
20
+
21
+ def homedir_owner
22
+ Etc.getpwuid(homedir_stat.uid).name
23
+ end
24
+
25
+ def homedir_group
26
+ Etc.getgrgid(homedir_stat.gid).name
27
+ end
28
+
29
+ def homedir_stat
30
+ @homedir_stat ||= File.stat(config.homedir)
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ require "boxen/preflight"
2
+
3
+ class Boxen::Preflight::EtcMyCnf < Boxen::Preflight
4
+ def run
5
+ abort "You have an /etc/my.cnf file.",
6
+ "This will confuse Boxen's MySQL a lot. Please remove it."
7
+ end
8
+
9
+ def ok?
10
+ !File.file? "/etc/my.cnf"
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ require "boxen/preflight"
2
+
3
+ class Boxen::Preflight::Identity < Boxen::Preflight
4
+ def ok?
5
+ !user || (config.email && config.name)
6
+ end
7
+
8
+ def run
9
+ config.email = user.email
10
+ config.name = user.name
11
+ end
12
+
13
+ def user
14
+ @user ||= config.api.user rescue nil
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ require "boxen/preflight"
2
+
3
+ class Boxen::Preflight::OS < Boxen::Preflight
4
+ SUPPORTED_RELEASES = %w(10.8 10.9 10.10 10.11)
5
+
6
+ def ok?
7
+ osx? && supported_release?
8
+ end
9
+
10
+ def run
11
+ abort "You must be running one of the following OS X versions: #{SUPPORTED_RELEASES.join(' ')}."
12
+ end
13
+
14
+ private
15
+
16
+ def osx?
17
+ `uname -s`.chomp == "Darwin"
18
+ end
19
+
20
+ def supported_release?
21
+ SUPPORTED_RELEASES.any? do |r|
22
+ current_release.start_with? r
23
+ end
24
+ end
25
+
26
+ def current_release
27
+ @current_release ||= `sw_vers -productVersion`
28
+ end
29
+ end