diff --git a/Gemfile.lock b/Gemfile.lock index 2d07ff3358..1539a70fac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -309,7 +309,7 @@ GEM multi_json (~> 1.0) pusher-signature (~> 0.1.8) pusher-signature (0.1.8) - rack (2.2.3) + rack (2.2.6.4) rack-attack (5.4.2) rack (>= 1.0, < 3) rack-contrib (2.2.0) diff --git a/lib/travis/api/app/access_token.rb b/lib/travis/api/app/access_token.rb index 47246f4ec0..5020fac15e 100644 --- a/lib/travis/api/app/access_token.rb +++ b/lib/travis/api/app/access_token.rb @@ -4,7 +4,7 @@ class Travis::Api::App class AccessToken DEFAULT_SCOPES = [:public, :private] - attr_reader :token, :scopes, :user_id, :app_id, :expires_in, :extra + attr_reader :token, :travis_token, :scopes, :user_id, :app_id, :expires_in, :extra def self.create(options = {}) new(options).tap(&:save) @@ -12,7 +12,7 @@ def self.create(options = {}) def self.for_travis_token(travis_token, options = {}) travis_token = Token.find_by_token(travis_token) unless travis_token.respond_to? :user - new(scope: :travis_token, app_id: 1, user: travis_token.user).tap(&:save) if travis_token + new(scope: :travis_token, app_id: 1, user: travis_token.user, travis_token: travis_token).tap(&:save) if travis_token end def self.find_by_token(token) @@ -32,12 +32,13 @@ def initialize(options = {}) raise ArgumentError, 'expires_in must be of integer type' end - @app_id = Integer(options[:app_id]) - @scopes = Array(options[:scopes] || options[:scope] || DEFAULT_SCOPES).map(&:to_sym) - @user = options[:user] - @user_id = Integer(options[:user_id] || @user.id) - @token = options[:token] || reuse_token || SecureRandom.urlsafe_base64(16) - @extra = options[:extra] + @app_id = Integer(options[:app_id]) + @scopes = Array(options[:scopes] || options[:scope] || DEFAULT_SCOPES).map(&:to_sym) + @user = options[:user] + @user_id = Integer(options[:user_id] || @user.id) + @token = options[:token] || reuse_token || SecureRandom.urlsafe_base64(16) + @travis_token = options[:travis_token] + @extra = options[:extra] end def save diff --git a/lib/travis/api/app/endpoint/authorization.rb b/lib/travis/api/app/endpoint/authorization.rb index a070431837..4de952692b 100644 --- a/lib/travis/api/app/endpoint/authorization.rb +++ b/lib/travis/api/app/endpoint/authorization.rb @@ -153,7 +153,11 @@ def update_first_login(user) def serialize_user(user) rendered = Travis::Api::Serialize.data(user, version: :v2) - rendered['user'].merge('token' => user.tokens.first.try(:token).to_s) + token = user.tokens.asset.first.try(:token).to_s + rendered['user'].merge( + 'token' => token, + 'rss_token' => user.tokens.rss.first.try(:token) || token, + ) end def oauth_endpoint diff --git a/lib/travis/api/app/endpoint/repos.rb b/lib/travis/api/app/endpoint/repos.rb index 7ad7aab50d..f241d2578e 100644 --- a/lib/travis/api/app/endpoint/repos.rb +++ b/lib/travis/api/app/endpoint/repos.rb @@ -22,6 +22,8 @@ class RepoStatus < Endpoint end get '/:owner_name/:name/builds', scope: [:public, :travis_token] do + halt 401 if scope == :travis_token && access_token.travis_token && !access_token.travis_token.rss? && access_token.user.tokens.rss.exists? + respond_with service(:find_builds, params), responder: :atom, responders: :atom end diff --git a/lib/travis/model/token.rb b/lib/travis/model/token.rb index 93f45ec9dd..1c9669cb62 100644 --- a/lib/travis/model/token.rb +++ b/lib/travis/model/token.rb @@ -7,6 +7,8 @@ # one) that they need use on their service hooks. This gives us some security # that people cannot throw random repositories at Travis CI. class Token < Travis::Model + enum purpose: [ :asset, :rss ] + belongs_to :user validates :token, :presence => true diff --git a/lib/travis/model/user.rb b/lib/travis/model/user.rb index 7f6c195eb2..3e6ad48e00 100644 --- a/lib/travis/model/user.rb +++ b/lib/travis/model/user.rb @@ -16,7 +16,7 @@ class User < Travis::Model has_many :custom_keys, as: :owner before_create :set_as_recent - after_create :create_a_token + after_create :create_the_tokens before_save :track_previous_changes serialize :github_scopes @@ -164,8 +164,9 @@ def inspect github_oauth_token ? super.gsub(github_oauth_token, '[REDACTED]') : super end - def create_a_token - self.tokens.create! + def create_the_tokens + self.tokens.asset.create! unless self.tokens.asset.exists? + self.tokens.rss.create! end def github? diff --git a/spec/lib/model/user_spec.rb b/spec/lib/model/user_spec.rb index ab5ad84984..6a4928d03b 100644 --- a/spec/lib/model/user_spec.rb +++ b/spec/lib/model/user_spec.rb @@ -182,4 +182,13 @@ def user(payload) end end end + + describe 'tokens' do + let(:user) { FactoryBot.create(:user) } + + it 'creates two tokens on creation' do + expect(user.tokens.asset.count).to eq(1) + expect(user.tokens.rss.count).to eq(1) + end + end end diff --git a/spec/unit/access_token_spec.rb b/spec/unit/access_token_spec.rb index 96c8216b06..0b9fb031a4 100644 --- a/spec/unit/access_token_spec.rb +++ b/spec/unit/access_token_spec.rb @@ -44,4 +44,15 @@ token = described_class.find_by_token(token.token) expect(token.extra).to eq({ 'required_params' => { 'job_id' => '1' } }) end + + it 'allows to save travis token' do + attrs = { + app_id: 1, + user_id: 3, + travis_token: Token.new + } + + token = described_class.new(attrs).tap(&:save) + expect(token.travis_token).to eq(attrs[:travis_token]) + end end diff --git a/spec/unit/endpoint/repos_spec.rb b/spec/unit/endpoint/repos_spec.rb index 92ba6f90f5..ea756c4340 100644 --- a/spec/unit/endpoint/repos_spec.rb +++ b/spec/unit/endpoint/repos_spec.rb @@ -48,4 +48,39 @@ end end end + + describe 'builds endpoint' do + let(:user) { FactoryBot.create(:user) } + let(:repo) { FactoryBot.create(:repository, private: false, owner_name: 'user', name: 'repo') } + + before { user.permissions.create(repository_id: repo.id, push: false) } + + context 'when user is authorizing with token' do + context 'and token is not a RSS one' do + let(:token) { user.tokens.asset.first } + + context 'and user has a RSS token' do + it 'responds with 401' do + expect(get("/repo_status/#{repo.owner_name}/#{repo.name}/builds.atom?token=#{token.token}", {}, {}).status).to eq(401) + end + end + + context 'and user does not have a RSS token' do + before { user.tokens.rss.delete_all } + + it 'responds with 200' do + expect(get("/repo_status/#{repo.owner_name}/#{repo.name}/builds.atom?token=#{token.token}", {}, {}).status).to eq(200) + end + end + end + + context 'and token is a RSS one' do + let(:token) { user.tokens.rss.first } + + it 'responds with 200' do + expect(get("/repo_status/#{repo.owner_name}/#{repo.name}/builds.atom?token=#{token.token}", {}, {}).status).to eq(200) + end + end + end + end end