Skip to content

Commit 345ba19

Browse files
committed
Merge pull request travis-ci#142 from travis-ci/rkh-track-user-agent
track and enforce user-agent
2 parents c3a2595 + c706576 commit 345ba19

File tree

8 files changed

+197
-15
lines changed

8 files changed

+197
-15
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ gem 'pry'
2525
gem 'metriks', '0.9.9.6'
2626
gem 'metriks-librato_metrics', github: 'eric/metriks-librato_metrics'
2727
gem 'micro_migrations'
28+
gem 'useragent'
2829

2930
group :test do
3031
gem 'rspec', '~> 2.13'

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ GEM
321321
kgio (~> 2.6)
322322
rack
323323
raindrops (~> 0.7)
324+
useragent (0.10.0)
324325
uuidtools (2.1.5)
325326
virtus (1.0.3)
326327
axiom-types (~> 0.1)
@@ -362,4 +363,5 @@ DEPENDENCIES
362363
travis-support!
363364
travis-yaml!
364365
unicorn
366+
useragent
365367
yard-sinatra!

lib/travis/api/app.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def initialize
114114
use Travis::Api::App::Middleware::Logging
115115
use Travis::Api::App::Middleware::Metriks
116116
use Travis::Api::App::Middleware::Rewrite
117+
use Travis::Api::App::Middleware::UserAgentTracker
117118

118119
SettingsEndpoint.subclass :env_vars
119120
if Travis.config.endpoints.ssh_key

lib/travis/api/app/cors.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Cors < Base
1414

1515
options // do
1616
headers['Access-Control-Allow-Methods'] = "HEAD, GET, POST, PATCH, PUT, DELETE"
17-
headers['Access-Control-Allow-Headers'] = "Content-Type, Authorization, Accept, If-None-Match, If-Modified-Since"
17+
headers['Access-Control-Allow-Headers'] = "Content-Type, Authorization, Accept, If-None-Match, If-Modified-Since, X-User-Agent"
1818
end
1919
end
2020
end
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
require 'travis/api/app'
2+
require 'useragent'
3+
4+
class Travis::Api::App
5+
class Middleware
6+
class UserAgentTracker < Middleware
7+
WEB_BROWSERS = [
8+
"Internet Explorer",
9+
"Webkit", "Chrome", "Safari", "Android",
10+
"Firefox", "Camino", "Iceweasel", "Seamonkey", "Android",
11+
"Opera", "Mozilla"
12+
]
13+
14+
before(agent: /^$/) do
15+
::Metriks.meter("api.user_agent.missing").mark
16+
halt(400, "error" => "missing User-Agent header") if Travis::Features.feature_active?(:require_user_agent)
17+
end
18+
19+
before(agent: /^.+$/) do
20+
agent = UserAgent.parse(request.user_agent)
21+
case agent.browser
22+
when *WEB_BROWSERS then mark_browser
23+
when "curl", "Wget" then mark(:console, agent.browser)
24+
when "travis-api-wrapper" then mark(:script, :node_js, agent.browser)
25+
when "TravisPy" then mark(:script, :python, agent.browser)
26+
when "Ruby", "PHP", "Perl", "Python" then mark(:script, agent.browser, :vanilla)
27+
when "Faraday" then mark(:script, :ruby, :vanilla)
28+
when "Travis" then mark_travis(agent)
29+
else mark_unknown
30+
end
31+
end
32+
33+
def mark_browser
34+
# allows a JavaScript Client to set X-User-Agent, for instance to "travis-web" in travis-web
35+
x_agent = UserAgent.parse(env['HTTP_X_USER_AGENT'] || 'unknown').browser
36+
mark(:browser, x_agent)
37+
end
38+
39+
def mark_travis(agent)
40+
command = agent.application.comment.detect { |c| c.start_with? "command " }
41+
42+
if command
43+
mark(:cli, :version, agent.version)
44+
mark(:cli, command.sub(' ', '.'))
45+
else
46+
# only track ruby version and library version for non-cli usage
47+
mark(:script, :ruby, :travis, :version, agent.version)
48+
end
49+
end
50+
51+
def mark_unknown
52+
logger.warn "[user-agent-tracker] Unknown User-Agent: %p" % request.user_agent
53+
mark(:unknown)
54+
end
55+
56+
def mark(*keys)
57+
key = "api.user_agent." << keys.map { |k| k.to_s.downcase.gsub(/[^a-z0-9\-\.]+/, '_') }.join('.')
58+
::Metriks.meter(key).mark
59+
end
60+
end
61+
end
62+
end

spec/unit/cors_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
end
4545

4646
it 'sets Access-Control-Allow-Headers' do
47-
headers['Access-Control-Allow-Headers'].should == "Content-Type, Authorization, Accept, If-None-Match, If-Modified-Since"
47+
headers['Access-Control-Allow-Headers'].should == "Content-Type, Authorization, Accept, If-None-Match, If-Modified-Since, X-User-Agent"
4848
end
4949
end
5050
end
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
require 'spec_helper'
2+
3+
describe Travis::Api::App::Middleware::UserAgentTracker do
4+
before do
5+
mock_app do
6+
use Travis::Api::App::Middleware::UserAgentTracker
7+
get('/') { 'ok' }
8+
end
9+
end
10+
11+
def expect_meter(name)
12+
Metriks.expects(:meter).with(name).returns(stub("meter", mark: nil))
13+
end
14+
15+
def get(env = {})
16+
env['HTTP_USER_AGENT'] ||= agent if agent
17+
super('/', {}, env)
18+
end
19+
20+
context 'missing User-Agent' do
21+
let(:agent) { }
22+
23+
it "tracks it" do
24+
expect_meter("api.user_agent.missing")
25+
get.should be_ok
26+
end
27+
28+
it "denies request if require_user_agent feature is enabled" do
29+
Travis::Features.expects(:feature_active?).with(:require_user_agent).returns(true)
30+
get.status.should be == 400
31+
end
32+
end
33+
34+
context 'web browser' do
35+
let(:agent) { "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.36 Safari/537.36" }
36+
37+
specify 'without X-User-Agent' do
38+
expect_meter("api.user_agent.browser.unknown")
39+
get
40+
end
41+
42+
specify 'with X-User-Agent' do
43+
expect_meter("api.user_agent.browser.travis-web")
44+
get('HTTP_X_USER_AGENT' => 'travis-web')
45+
end
46+
end
47+
48+
context 'console' do
49+
let(:agent) { 'curl' }
50+
specify do
51+
expect_meter("api.user_agent.console.curl")
52+
get
53+
end
54+
end
55+
56+
context 'travis-api-wrapper' do
57+
let(:agent) { 'travis-api-wrapper - v0.01 - ([email protected])' }
58+
specify do
59+
expect_meter("api.user_agent.script.node_js.travis-api-wrapper")
60+
get
61+
end
62+
end
63+
64+
context 'TravisPy' do
65+
let(:agent) { 'TravisPy' }
66+
specify do
67+
expect_meter("api.user_agent.script.python.travispy")
68+
get
69+
end
70+
end
71+
72+
context 'Ruby' do
73+
let(:agent) { 'Ruby' }
74+
specify do
75+
expect_meter("api.user_agent.script.ruby.vanilla")
76+
get
77+
end
78+
end
79+
80+
context 'Faraday' do
81+
let(:agent) { 'Faraday' }
82+
specify do
83+
expect_meter("api.user_agent.script.ruby.vanilla")
84+
get
85+
end
86+
end
87+
88+
context 'travis.rb' do
89+
let(:agent) { 'Travis/1.6.8 (Mac OS X 10.9.2 like Darwin; Ruby 2.1.1p42; RubyGems 2.0.14) Faraday/0.8.9 Typhoeus/0.6.7' }
90+
specify do
91+
expect_meter("api.user_agent.script.ruby.travis.version.1.6.8")
92+
get
93+
end
94+
end
95+
96+
context 'Travis CLI' do
97+
let(:agent) { 'Travis/1.6.8 (Mac OS X 10.10.2 like Darwin; Ruby 2.1.1; RubyGems 2.0.14; command whoami) Faraday/0.8.9 Typhoeus/0.6.7' }
98+
specify do
99+
expect_meter("api.user_agent.cli.version.1.6.8")
100+
expect_meter("api.user_agent.cli.command.whoami")
101+
get
102+
end
103+
end
104+
end

travis-api.gemspec

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,18 @@ Gem::Specification.new do |s|
1818
"Henrik Hodne",
1919
"Andre Arko",
2020
"Erik Michaels-Ober",
21-
"Brian Ford",
2221
"Steve Richert",
23-
"rainsun",
24-
"Bryan Goldstein",
25-
"James Dennes",
26-
"Nick Schonning",
22+
"Brian Ford",
2723
"Patrick Williams",
24+
"Bryan Goldstein",
2825
"Puneeth Chaganti",
2926
"Thais Camilo and Konstantin Haase",
3027
"Tim Carey-Smith",
31-
"Zachary Scott"
28+
"Zachary Scott",
29+
"James Dennes",
30+
"rainsun",
31+
"Dan Rice",
32+
"Nick Schonning"
3233
]
3334

3435
s.email = [
@@ -44,17 +45,19 @@ Gem::Specification.new do |s|
4445
4546
4647
47-
"bford@engineyard.com",
48+
"henrik@travis-ci.com",
4849
49-
50+
51+
52+
5053
54+
5155
52-
5356
5457
5558
56-
57-
"brysgo@gmail.com"
59+
60+
"patrick@bittorrent.com"
5861
]
5962

6063
s.files = [
@@ -65,7 +68,6 @@ Gem::Specification.new do |s|
6568
"bin/start-nginx",
6669
"config.ru",
6770
"config/database.yml",
68-
"config/nginx.conf.erb",
6971
"config/puma-config.rb",
7072
"config/unicorn.rb",
7173
"lib/tasks/build_update_branch.rake",
@@ -83,6 +85,7 @@ Gem::Specification.new do |s|
8385
"lib/travis/api/app/endpoint/builds.rb",
8486
"lib/travis/api/app/endpoint/documentation.rb",
8587
"lib/travis/api/app/endpoint/endpoints.rb",
88+
"lib/travis/api/app/endpoint/env_vars.rb",
8689
"lib/travis/api/app/endpoint/home.rb",
8790
"lib/travis/api/app/endpoint/hooks.rb",
8891
"lib/travis/api/app/endpoint/jobs.rb",
@@ -91,6 +94,7 @@ Gem::Specification.new do |s|
9194
"lib/travis/api/app/endpoint/repos.rb",
9295
"lib/travis/api/app/endpoint/requests.rb",
9396
"lib/travis/api/app/endpoint/setting_endpoint.rb",
97+
"lib/travis/api/app/endpoint/singleton_settings_endpoint.rb",
9498
"lib/travis/api/app/endpoint/uptime.rb",
9599
"lib/travis/api/app/endpoint/users.rb",
96100
"lib/travis/api/app/extensions.rb",
@@ -119,6 +123,7 @@ Gem::Specification.new do |s|
119123
"lib/travis/api/app/responders/plain.rb",
120124
"lib/travis/api/app/responders/service.rb",
121125
"lib/travis/api/app/responders/xml.rb",
126+
"lib/travis/api/app/services/schedule_request.rb",
122127
"lib/travis/api/serializer.rb",
123128
"lib/travis/api/v2.rb",
124129
"lib/travis/api/v2/http.rb",
@@ -143,11 +148,13 @@ Gem::Specification.new do |s|
143148
"lib/travis/api/v2/http/request.rb",
144149
"lib/travis/api/v2/http/requests.rb",
145150
"lib/travis/api/v2/http/ssh_key.rb",
146-
"lib/travis/api/v2/http/ssh_keys.rb",
147151
"lib/travis/api/v2/http/ssl_key.rb",
148152
"lib/travis/api/v2/http/user.rb",
149153
"lib/travis/api/v2/http/validation_error.rb",
154+
"lib/travis/private_key.rb",
150155
"public/favicon.ico",
156+
"public/images/result/canceled.png",
157+
"public/images/result/canceled.svg",
151158
"public/images/result/error.png",
152159
"public/images/result/error.svg",
153160
"public/images/result/failing.png",
@@ -159,12 +166,14 @@ Gem::Specification.new do |s|
159166
"public/images/result/unknown.png",
160167
"public/images/result/unknown.svg",
161168
"script/console",
169+
"script/repos_stats.rb",
162170
"script/server",
163171
"spec/integration/formats_handling_spec.rb",
164172
"spec/integration/responders_spec.rb",
165173
"spec/integration/routes.backup.rb",
166174
"spec/integration/scopes_spec.rb",
167175
"spec/integration/settings_endpoint_spec.rb",
176+
"spec/integration/singleton_settings_endpoint_spec.rb",
168177
"spec/integration/uptime_spec.rb",
169178
"spec/integration/v1/branches_spec.rb",
170179
"spec/integration/v1/builds_spec.rb",
@@ -179,6 +188,7 @@ Gem::Specification.new do |s|
179188
"spec/integration/v2/repositories_spec.rb",
180189
"spec/integration/v2/requests_spec.rb",
181190
"spec/integration/v2/settings/env_vars_spec.rb",
191+
"spec/integration/v2/settings/ssh_key_spec.rb",
182192
"spec/integration/v2/users_spec.rb",
183193
"spec/integration/v2_spec.backup.rb",
184194
"spec/integration/version_spec.rb",
@@ -194,6 +204,7 @@ Gem::Specification.new do |s|
194204
"spec/unit/api/v2/http/build_spec.rb",
195205
"spec/unit/api/v2/http/builds_spec.rb",
196206
"spec/unit/api/v2/http/caches_spec.rb",
207+
"spec/unit/api/v2/http/env_var_spec.rb",
197208
"spec/unit/api/v2/http/hooks_spec.rb",
198209
"spec/unit/api/v2/http/job_spec.rb",
199210
"spec/unit/api/v2/http/jobs_spec.rb",
@@ -219,6 +230,7 @@ Gem::Specification.new do |s|
219230
"spec/unit/endpoint/lint_spec.rb",
220231
"spec/unit/endpoint/logs_spec.rb",
221232
"spec/unit/endpoint/repos_spec.rb",
233+
"spec/unit/endpoint/requests_spec.rb",
222234
"spec/unit/endpoint/users_spec.rb",
223235
"spec/unit/endpoint_spec.rb",
224236
"spec/unit/extensions/expose_pattern_spec.rb",

0 commit comments

Comments
 (0)