diff --git a/app/controllers/api/v1/aspects_controller.rb b/app/controllers/api/v1/aspects_controller.rb index 2a73a73e0..dd09ea497 100644 --- a/app/controllers/api/v1/aspects_controller.rb +++ b/app/controllers/api/v1/aspects_controller.rb @@ -3,12 +3,12 @@ module Api module V1 class AspectsController < Api::V1::BaseController - before_action only: %i[index show] do - require_access_token %w[read] + before_action except: %i[create update destroy] do + require_access_token %w[contacts:read] end before_action only: %i[create update destroy] do - require_access_token %w[read write] + require_access_token %w[contacts:modify] end def index diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index 7e0f5137e..d7c5f350c 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -52,6 +52,14 @@ module Api def time_pager(query) Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params) end + + def private_read? + access_token? %w[private:read] + end + + def private_modify? + access_token? %w[private:modify] + end end end end diff --git a/app/controllers/api/v1/comments_controller.rb b/app/controllers/api/v1/comments_controller.rb index 7b00151c4..d147a664d 100644 --- a/app/controllers/api/v1/comments_controller.rb +++ b/app/controllers/api/v1/comments_controller.rb @@ -3,12 +3,12 @@ module Api module V1 class CommentsController < Api::V1::BaseController - before_action only: %i[index report] do - require_access_token %w[read] + before_action do + require_access_token %w[interactions public:read] end before_action only: %i[create destroy] do - require_access_token %w[write] + require_access_token %w[interactions public:modify] end rescue_from ActiveRecord::RecordNotFound do @@ -29,6 +29,7 @@ module Api end def index + find_post comments_query = comment_service.find_for_post(params[:post_id]) params[:after] = Time.utc(1900).iso8601 if params.permit(:before, :after).empty? comments_page = time_pager(comments_query).response @@ -37,6 +38,7 @@ module Api end def destroy + find_post if comment_and_post_validate(params[:post_id], params[:id]) comment_service.destroy!(params[:id]) head :no_content @@ -46,6 +48,7 @@ module Api end def report + find_post post_guid = params.require(:post_id) comment_guid = params.require(:comment_id) return unless comment_and_post_validate(post_guid, comment_guid) @@ -94,9 +97,19 @@ module Api @comment_service ||= CommentService.new(current_user) end + def post_service + @post_service ||= PostService.new(current_user) + end + def comment_as_json(comment) CommentPresenter.new(comment).as_api_response end + + def find_post + post = post_service.find!(params[:post_id]) + return post if post.public? || private_read? + raise ActiveRecord::RecordNotFound + end end end end diff --git a/app/controllers/api/v1/contacts_controller.rb b/app/controllers/api/v1/contacts_controller.rb index f72c68805..2a1f1bda3 100644 --- a/app/controllers/api/v1/contacts_controller.rb +++ b/app/controllers/api/v1/contacts_controller.rb @@ -5,12 +5,12 @@ require "api/paging/index_paginator" module Api module V1 class ContactsController < Api::V1::BaseController - before_action only: %i[index] do - require_access_token %w[read] + before_action except: %i[create destroy] do + require_access_token %w[contacts:read] end before_action only: %i[create destroy] do - require_access_token %w[read write] + require_access_token %w[contacts:modify] end rescue_from ActiveRecord::RecordNotFound do diff --git a/app/controllers/api/v1/conversations_controller.rb b/app/controllers/api/v1/conversations_controller.rb index 99659f040..e25863c9a 100644 --- a/app/controllers/api/v1/conversations_controller.rb +++ b/app/controllers/api/v1/conversations_controller.rb @@ -5,12 +5,8 @@ module Api class ConversationsController < Api::V1::BaseController include ConversationsHelper - before_action only: %i[create index show] do - require_access_token %w[read] - end - - before_action only: %i[create destroy] do - require_access_token %w[read write] + before_action do + require_access_token %w[conversations] end rescue_from ActiveRecord::RecordNotFound do diff --git a/app/controllers/api/v1/likes_controller.rb b/app/controllers/api/v1/likes_controller.rb index 46525b951..a9e48c4fe 100644 --- a/app/controllers/api/v1/likes_controller.rb +++ b/app/controllers/api/v1/likes_controller.rb @@ -3,12 +3,8 @@ module Api module V1 class LikesController < Api::V1::BaseController - before_action only: %i[show] do - require_access_token %w[read] - end - - before_action only: %i[create destroy] do - require_access_token %w[write] + before_action do + require_access_token %w[interactions public:read] end rescue_from ActiveRecord::RecordNotFound do @@ -16,10 +12,12 @@ module Api end rescue_from ActiveRecord::RecordInvalid do - render json: I18n.t("api.endpoint_errors.likes.user_not_allowed_to_like"), status: :not_found + render json: I18n.t("api.endpoint_errors.likes.user_not_allowed_to_like"), status: :unprocessable_entity end def show + post = post_service.find!(params[:post_id]) + raise ActiveRecord::RecordInvalid unless post.public? || private_read? likes_query = like_service.find_for_post(params[:post_id]) likes_page = index_pager(likes_query).response likes_page[:data] = likes_page[:data].map {|x| like_json(x) } @@ -27,6 +25,8 @@ module Api end def create + post = post_service.find!(params[:post_id]) + raise ActiveRecord::RecordInvalid unless post.public? || private_modify? like_service.create(params[:post_id]) rescue ActiveRecord::RecordInvalid => e return render json: I18n.t("api.endpoint_errors.likes.like_exists"), status: :unprocessable_entity if @@ -37,6 +37,8 @@ module Api end def destroy + post = post_service.find!(params[:post_id]) + raise ActiveRecord::RecordInvalid unless post.public? || private_modify? success = like_service.unlike_post(params[:post_id]) if success head :no_content @@ -45,11 +47,15 @@ module Api end end + private + def like_service @like_service ||= LikeService.new(current_user) end - private + def post_service + @post_service ||= PostService.new(current_user) + end def like_json(like) LikesPresenter.new(like).as_api_json diff --git a/app/controllers/api/v1/messages_controller.rb b/app/controllers/api/v1/messages_controller.rb index a1b92a203..b7a085743 100644 --- a/app/controllers/api/v1/messages_controller.rb +++ b/app/controllers/api/v1/messages_controller.rb @@ -3,12 +3,8 @@ module Api module V1 class MessagesController < Api::V1::BaseController - before_action only: %i[create] do - require_access_token %w[read write] - end - - before_action only: %i[index] do - require_access_token %w[read] + before_action do + require_access_token %w[conversations] end rescue_from ActiveRecord::RecordNotFound do diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 3e7fbd445..cd710f9b0 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -3,12 +3,8 @@ module Api module V1 class NotificationsController < Api::V1::BaseController - before_action except: %i[update] do - require_access_token %w[read] - end - - before_action only: %i[update] do - require_access_token %w[write] + before_action do + require_access_token %w[notifications] end rescue_from ActiveRecord::RecordNotFound do diff --git a/app/controllers/api/v1/photos_controller.rb b/app/controllers/api/v1/photos_controller.rb index 45d8d147c..8b8cdbc9f 100644 --- a/app/controllers/api/v1/photos_controller.rb +++ b/app/controllers/api/v1/photos_controller.rb @@ -4,11 +4,11 @@ module Api module V1 class PhotosController < Api::V1::BaseController before_action except: %i[create destroy] do - require_access_token %w[read] + require_access_token %w[public:read] end before_action only: %i[create destroy] do - require_access_token %w[read write] + require_access_token %w[public:modify] end rescue_from ActiveRecord::RecordNotFound do @@ -16,7 +16,12 @@ module Api end def index - photos_page = time_pager(current_user.photos).response + query = if private_read? + current_user.photos + else + current_user.photos.where(public: true) + end + photos_page = time_pager(query).response photos_page[:data] = photos_page[:data].map {|photo| PhotoPresenter.new(photo).as_api_json(true) } render json: photos_page end @@ -24,11 +29,14 @@ module Api def show photo = photo_service.visible_photo(params.require(:id)) raise ActiveRecord::RecordNotFound unless photo + raise ActiveRecord::RecordNotFound unless photo.public? || private_read? render json: PhotoPresenter.new(photo).as_api_json(true) end def create image = params.require(:image) + public_photo = params.has_key?(:aspect_ids) + raise RuntimeError unless public_photo || private_modify? base_params = params.permit(:aspect_ids, :pending, :set_profile_photo) photo = photo_service.create_from_params_and_file(base_params, image) raise RuntimeError unless photo @@ -40,6 +48,7 @@ module Api def destroy photo = current_user.photos.where(guid: params[:id]).first raise ActiveRecord::RecordNotFound unless photo + raise ActiveRecord::RecordNotFound unless photo.public? || private_modify? if current_user.retract(photo) head :no_content else diff --git a/app/controllers/api/v1/post_interactions_controller.rb b/app/controllers/api/v1/post_interactions_controller.rb index c5d6e1697..4e313964a 100644 --- a/app/controllers/api/v1/post_interactions_controller.rb +++ b/app/controllers/api/v1/post_interactions_controller.rb @@ -6,7 +6,7 @@ module Api include PostsHelper before_action do - require_access_token %w[read write] + require_access_token %w[public:read interactions] end rescue_from ActiveRecord::RecordNotFound do @@ -14,7 +14,7 @@ module Api end def subscribe - post = post_service.find!(params[:post_id]) + post = find_post current_user.participate!(post) head :no_content rescue ActiveRecord::RecordInvalid @@ -22,13 +22,13 @@ module Api end def hide - post = post_service.find!(params[:post_id]) + post = find_post current_user.toggle_hidden_shareable(post) head :no_content end def mute - post = post_service.find!(params[:post_id]) + post = find_post participation = current_user.participations.find_by!(target_id: post.id) participation.destroy head :no_content @@ -36,7 +36,7 @@ module Api def report reason = params.require(:reason) - post = post_service.find!(params[:post_id]) + post = find_post report = current_user.reports.new( item_id: post.id, item_type: "Post", @@ -53,7 +53,7 @@ module Api def vote begin - post = post_service.find!(params[:post_id]) + post = find_post rescue ActiveRecord::RecordNotFound render json: I18n.t("api.endpoint_errors.posts.post_not_found"), status: :not_found return @@ -77,6 +77,12 @@ module Api def poll_service @poll_service ||= PollParticipationService.new(current_user) end + + def find_post + post = post_service.find!(params[:post_id]) + raise ActiveRecord::RecordNotFound unless post.public? || private_read? + post + end end end end diff --git a/app/controllers/api/v1/posts_controller.rb b/app/controllers/api/v1/posts_controller.rb index 673d78b96..707d61dc7 100644 --- a/app/controllers/api/v1/posts_controller.rb +++ b/app/controllers/api/v1/posts_controller.rb @@ -5,12 +5,12 @@ module Api class PostsController < Api::V1::BaseController include PostsHelper - before_action only: :show do - require_access_token %w[read] + before_action except: %i[create destroy] do + require_access_token %w[public:read] end before_action only: %i[create destroy] do - require_access_token %w[read write] + require_access_token %w[public:modify] end rescue_from ActiveRecord::RecordNotFound do @@ -18,14 +18,13 @@ module Api end def show - mark_notifications = - params[:mark_notifications].present? && params[:mark_notifications] post = post_service.find!(params[:id]) - post_service.mark_user_notifications(post.id) if mark_notifications + raise ActiveRecord::RecordNotFound unless post.public? || private_read? render json: post_as_json(post) end def create + raise StandardError unless params.require(:public) || private_modify? status_service = StatusMessageCreationService.new(current_user) creation_params = normalized_create_params @status_message = status_service.create(creation_params) @@ -35,9 +34,9 @@ module Api end def destroy - post_service.destroy(params[:id]) + post_service.destroy(params[:id], private_modify?) head :no_content - rescue Diaspora::NotMine + rescue Diaspora::NotMine, Diaspora::NonPublic render json: I18n.t("api.endpoint_errors.posts.failed_delete"), status: :forbidden end diff --git a/app/controllers/api/v1/reshares_controller.rb b/app/controllers/api/v1/reshares_controller.rb index 6781ef77c..546d58716 100644 --- a/app/controllers/api/v1/reshares_controller.rb +++ b/app/controllers/api/v1/reshares_controller.rb @@ -3,12 +3,12 @@ module Api module V1 class ResharesController < Api::V1::BaseController - before_action only: %i[show] do - require_access_token %w[read] + before_action except: %i[create] do + require_access_token %w[public:read] end before_action only: %i[create] do - require_access_token %w[read write] + require_access_token %w[public:modify] end rescue_from ActiveRecord::RecordNotFound do diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb index 42e7a846b..0d7b34c0f 100644 --- a/app/controllers/api/v1/search_controller.rb +++ b/app/controllers/api/v1/search_controller.rb @@ -4,7 +4,7 @@ module Api module V1 class SearchController < Api::V1::BaseController before_action do - require_access_token %w[read] + require_access_token %w[public:read] end rescue_from ActionController::ParameterMissing, RuntimeError do @@ -15,17 +15,27 @@ module Api parameters = params.permit(:tag, :name_or_handle) raise RuntimeError if parameters.keys.length != 1 people_query = if params.has_key?(:tag) - Person.profile_tagged_with(params[:tag]) - else - Person.search(params[:name_or_handle], current_user) - end + Person.profile_tagged_with(params[:tag]) + else + connected_only = !private_read? + Person.search( + params[:name_or_handle], + current_user, + only_contacts: connected_only, + mutual: connected_only + ) + end user_page = index_pager(people_query).response user_page[:data] = user_page[:data].map {|p| PersonPresenter.new(p).as_api_json } render json: user_page end def post_index - posts_query = Stream::Tag.new(current_user, params.require(:tag)).posts + posts_query = if private_read? + Stream::Tag.new(current_user, params.require(:tag)).posts + else + Stream::Tag.new(nil, params.require(:tag)).posts + end posts_page = time_pager(posts_query, "posts.created_at", "created_at").response posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post).as_api_response } render json: posts_page diff --git a/app/controllers/api/v1/streams_controller.rb b/app/controllers/api/v1/streams_controller.rb index 04c922a0d..efc5e4ec6 100644 --- a/app/controllers/api/v1/streams_controller.rb +++ b/app/controllers/api/v1/streams_controller.rb @@ -4,7 +4,15 @@ module Api module V1 class StreamsController < Api::V1::BaseController before_action do - require_access_token %w[read] + require_access_token %w[public:read] + end + + before_action only: %w[aspects] do + require_access_token %w[contacts:read private:read] + end + + before_action only: %w[followed_tags] do + require_access_token %w[tags:read] end def aspects @@ -41,7 +49,9 @@ module Api def stream_responder(stream_klass=nil, query_time_field="posts.created_at", data_time_field="created_at") @stream = stream_klass.present? ? stream_klass.new(current_user, max_time: stream_max_time) : @stream - posts_page = pager(@stream.stream_posts, query_time_field, data_time_field).response + query = @stream.stream_posts + query = query.where(public: true) unless private_read? + posts_page = pager(query, query_time_field, data_time_field).response posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response } posts_page[:links].delete(:previous) render json: posts_page diff --git a/app/controllers/api/v1/tag_followings_controller.rb b/app/controllers/api/v1/tag_followings_controller.rb index a8ed8ebfa..f2181972d 100755 --- a/app/controllers/api/v1/tag_followings_controller.rb +++ b/app/controllers/api/v1/tag_followings_controller.rb @@ -3,16 +3,12 @@ module Api module V1 class TagFollowingsController < Api::V1::BaseController - before_action only: %i[index] do - require_access_token %w[read] + before_action except: %i[create destroy] do + require_access_token %w[tags:read] end before_action only: %i[create destroy] do - require_access_token %w[read write] - end - - rescue_from StandardError do - render json: I18n.t("api.endpoint_errors.tags.cant_process"), status: :bad_request + require_access_token %w[tags:modify] end def index @@ -22,6 +18,8 @@ module Api def create tag_followings_service.create(params.require(:name)) head :no_content + rescue StandardError + render json: I18n.t("api.endpoint_errors.tags.cant_process"), status: :unprocessable_entity end def destroy diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb index 3ceb9c801..9d44e7518 100644 --- a/app/controllers/api/v1/users_controller.rb +++ b/app/controllers/api/v1/users_controller.rb @@ -5,12 +5,20 @@ module Api class UsersController < Api::V1::BaseController include TagsHelper - before_action except: %i[update] do - require_access_token %w[read] + before_action except: %i[contacts update show] do + require_access_token %w[public:read] end before_action only: %i[update] do - require_access_token %w[write] + require_access_token %w[profile:modify] + end + + before_action only: %i[contacts] do + require_access_token %w[contacts:read] + end + + before_action only: %i[show] do + require_access_token %w[profile] end rescue_from ActiveRecord::RecordNotFound do @@ -19,7 +27,9 @@ module Api def show person = if params.has_key?(:id) - Person.find_by!(guid: params[:id]) + found_person = Person.find_by!(guid: params[:id]) + raise ActiveRecord::RecordNotFound unless found_person.searchable || access_token?("contacts:read") + found_person else current_user.person end @@ -52,7 +62,8 @@ module Api def photos person = Person.find_by!(guid: params[:user_id]) - photos_query = Photo.visible(current_user, person, :all, Time.current) + user_for_query = current_user if private_read? + photos_query = Photo.visible(user_for_query, person, :all, Time.current) photos_page = time_pager(photos_query).response photos_page[:data] = photos_page[:data].map {|photo| PhotoPresenter.new(photo).as_api_json(true) } render json: photos_page @@ -60,7 +71,11 @@ module Api def posts person = Person.find_by!(guid: params[:user_id]) - posts_query = current_user.posts_from(person, false) + posts_query = if private_read? + current_user.posts_from(person, false) + else + Post.where(author_id: person.id, public: true) + end posts_page = time_pager(posts_query).response posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response } render json: posts_page diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 597c815f7..6a89d63aa 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -18,7 +18,32 @@ module Api scope :with_redirect_uri, ->(given_uri) { where redirect_uri: given_uri } - SCOPES = %w(openid sub aud name nickname profile picture read write) + SCOPES = %w[ + birthdate + contacts:modify + contacts:read + conversations + email + gender + interactions + locale + name + nickname + notifications + openid + picture + private:modify + private:read + profile + profile + profile:modify + public:modify + public:read + sub + tags:modify + tags:read + updated_at + ].freeze def setup self.refresh_token = SecureRandom.hex(32) diff --git a/app/services/post_service.rb b/app/services/post_service.rb index dd320f282..34b4f5ab4 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -35,8 +35,12 @@ class PostService mark_mention_notifications_read(post_id) end - def destroy(post_id) - post = find!(post_id) + def destroy(post_id, private_allowed=true) + post = if private_allowed + find_non_public_by_guid_or_id_with_user!(post_id) + else + find_public!(post_id) + end raise Diaspora::NotMine unless post.author == user.person user.retract(post) end diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 93885ebc9..644d6c237 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -905,6 +905,8 @@ en: deny: "Deny" bad_request: "Missing client id or redirect URI" client_id_not_found: "No client with client_id %{client_id} with redirect URI %{redirect_uri} found" + private_contacts_linkage_error: "private:read and private:modify require contacts:read as well" + unknown_scope: "Unknown scope: #{scope_name}" destroy: fail: "The attempt to revoke the authorization with ID %{id} failed" user_applications: @@ -920,31 +922,73 @@ en: scopes: openid: name: "basic profile" - description: "This allows the application to read your basic profile" + description: "This grants read access to basic profile information to the application." sub: name: "sub" - description: "This grants sub permissions to the application" - aud: - name: "aud" - description: "This grants aud permissions to the application" + description: "This grants sub permissions to the application." name: name: "name" - description: "This grants name permissions to the application" + description: "This grants read access to name data to the application." nickname: name: "nickname" - description: "This grants nickname permissions to the application" - profile: - name: "extended profile" - description: "This allows the application to read your extended profile" + description: "This grants read access to nickname data to the application." picture: name: "picture" - description: "This grants picture permissions to the application" - read: - name: "read profile, stream and conversations" - description: "This allows the application to read your stream, your conversations and your complete profile" - write: - name: "send posts, conversations and reactions" - description: "This allows the application to send new posts, write conversations, and send reactions" + description: "This grants read access to user's profile picture data to the application." + gender: + name: "gender" + description: "This grants read access to the user's gendere data to the application." + birthdate: + name: "birthdate" + description: "This grants read access ot the user's birthdate to the application." + locale: + name: "locale" + description: "This grants read access ot the user's locale to the application." + updated_at: + name: "updated_at" + description: "This grants read access to the user's profile update time to the application." + 'contacts:read': + name: "contacts:read" + description: "This grants read permissions to contacts and related data (like aspects) to the application." + 'contacts:modify': + name: "contacts:modify" + description: "This grants write permissions to contacts and related data (like aspects) to the application." + conversations: + name: "conversations" + description: "This grants read/write permission to private messaging to the application." + email: + name: "email" + description: "This grants read access to the user's email address to the application." + interactions: + name: "interactions" + description: "This grants read and write permissions on interacting wth posts to the application." + notifications: + name: "notifications" + description: "This grants read and write permissions on interactions for the user to the application." + 'private:read': + name: "private:read" + description: "This grants read privileges on private posts and streams to the application." + 'private:modify': + name: "private:modify" + description: "This grants modification privileges on private posts and streams, including allowing private sharing, to the application." + 'public:read': + name: "public:read" + description: "This grants read permissions on public posts, interactions, and user data to the application." + 'public:modify': + name: "updated_at" + description: "This grants write permissions on public posts (including public post writing), interactions, and user data to the application." + profile: + name: "extended profile" + description: "This grants read access to extendend profile data to the application." + 'profile:modify': + name: "profile:modify" + description: "This grants user profile update permissions to the application." + 'tags:read': + name: "tags:read" + description: "This grants read access to user's followed tags and tag streams to the application." + 'tags:modify': + name: "tags:modify" + description: "This grants modification access to user's followed tags to the application." error_page: title: "Oh! Something went wrong :(" contact_developer: "You should contact the developer of the application and include the following detailed error message:" diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb index c88a43a09..ce108ce82 100644 --- a/lib/api/openid_connect/authorization_point/endpoint.rb +++ b/lib/api/openid_connect/authorization_point/endpoint.rb @@ -50,9 +50,16 @@ module Api replace_profile_scope_with_specific_claims(req) @scopes = req.scope.map {|scope| scope.tap do |scope_name| - req.invalid_scope! "Unknown scope: #{scope_name}" unless auth_scopes.include? scope_name + req.invalid_scope! I18n.t("api.openid_connect.authorizations.new.unknown_scope") \ + unless auth_scopes.include?(scope_name) end } + + @scopes.push("public:read") unless @scopes.include?("public:read") + has_private_scope = @scopes.include?("private:read") || @scopes.include?("private:modify") + has_contacts_scope = @scopes.include? "contacts:read" + req.invalid_scope! I18n.t("api.openid_connect.authorizations.new.private_contacts_linkage_error") \ + if has_private_scope && !has_contacts_scope end def auth_scopes diff --git a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb index 7ce6b6b5d..4496eb53a 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb @@ -17,7 +17,7 @@ module Api end def replace_profile_scope_with_specific_claims(req) - profile_claims = %w(sub aud name nickname profile picture) + profile_claims = %w[sub name nickname profile picture gender birthdate locale updated_at] scopes_as_claims = req.scope.flat_map {|scope| scope == "profile" ? profile_claims : [scope] }.uniq req.update_param("scope", scopes_as_claims) end diff --git a/lib/api/openid_connect/protected_resource_endpoint.rb b/lib/api/openid_connect/protected_resource_endpoint.rb index b75513183..23be2fc4c 100644 --- a/lib/api/openid_connect/protected_resource_endpoint.rb +++ b/lib/api/openid_connect/protected_resource_endpoint.rb @@ -29,11 +29,15 @@ module Api attr_reader :current_token def require_access_token(required_scopes) + raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) unless + access_token?(required_scopes) + end + + def access_token?(required_scopes) @current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN] raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user") unless @current_token && @current_token.authorization - raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) unless - @current_token.authorization.try(:accessible?, required_scopes) + @current_token.authorization.try(:accessible?, required_scopes) end end end diff --git a/lib/api/openid_connect/token_endpoint.rb b/lib/api/openid_connect/token_endpoint.rb index b9b9d39e9..96fa44632 100644 --- a/lib/api/openid_connect/token_endpoint.rb +++ b/lib/api/openid_connect/token_endpoint.rb @@ -27,6 +27,7 @@ module Api auth = Api::OpenidConnect::Authorization.with_redirect_uri(req.redirect_uri).use_code(req.code) req.invalid_grant! if auth.blank? res.access_token = auth.create_access_token + res.access_token.refresh_token = auth.refresh_token if auth.accessible? "openid" id_token = auth.create_id_token res.id_token = id_token.to_jwt(access_token: res.access_token) diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 7ce14838a..a62966306 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -253,7 +253,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :request do before do get new_api_openid_connect_authorization_path, params: {client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", - scope: "openid read", nonce: 413_093_098_3, state: 413_093_098_3} + scope: "openid interactions", nonce: 413_093_098_3, state: 413_093_098_3} end it "should receive another authorization request" do @@ -380,12 +380,71 @@ describe Api::OpenidConnect::AuthorizationsController, type: :request do end end end + + context "ensure public:read always there" do + it "added with only openid" do + get new_api_openid_connect_authorization_path, params: {client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token token", + scope: "openid", + nonce: 418_093_098_3, + state: 418_093_098_3} + post api_openid_connect_authorizations_path, params: {approve: "true"} + expect(alice.reload.authorizations.first.scopes).to include("public:read") + end + end + + context "with contacts:read and private linkage requirement" do + it "fails without contacts:read on private:read" do + scopes = "openid private:read" + get new_api_openid_connect_authorization_path, params: {client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token token", + scope: scopes, + nonce: 418_093_098_3, + state: 418_093_098_3} + expect(response.status).to eq(302) + end + + it "fails without contacts:read on private:modify" do + scopes = "openid private:modify" + get new_api_openid_connect_authorization_path, params: {client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token token", + scope: scopes, + nonce: 418_093_098_3, + state: 418_093_098_3} + expect(response.status).to eq(302) + end + + it "succeeds with contacts:read on private:read" do + scopes = "openid contacts:read private:read" + get new_api_openid_connect_authorization_path, params: {client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token token", + scope: scopes, + nonce: 418_093_098_3, + state: 418_093_098_3} + expect(response.status).to eq(200) + end + + it "succeeds with contacts:read on private:modify" do + scopes = "openid contacts:read private:modify" + get new_api_openid_connect_authorization_path, params: {client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token token", + scope: scopes, + nonce: 418_093_098_3, + state: 418_093_098_3} + expect(response.status).to eq(200) + end + end end describe "#destroy" do context "with existent authorization" do it "removes the authorization" do - auth_with_read = FactoryGirl.create(:auth_with_read, o_auth_application: client) + auth_with_read = FactoryGirl.create(:auth_with_profile_only, o_auth_application: client) delete api_openid_connect_authorization_path(auth_with_read.id) expect(Api::OpenidConnect::Authorization.find_by(id: auth_with_read.id)).to be_nil end diff --git a/spec/controllers/api/openid_connect/token_endpoint_controller_spec.rb b/spec/controllers/api/openid_connect/token_endpoint_controller_spec.rb index 20cf9ba44..5d9d28803 100644 --- a/spec/controllers/api/openid_connect/token_endpoint_controller_spec.rb +++ b/spec/controllers/api/openid_connect/token_endpoint_controller_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe Api::OpenidConnect::TokenEndpointController, type: :controller, suppress_csrf_verification: :none do - let(:auth) { FactoryGirl.create(:auth_with_read) } + let(:auth) { FactoryGirl.create(:auth_with_profile_only) } describe "#create" do it "returns 200 on success" do @@ -15,5 +15,16 @@ describe Api::OpenidConnect::TokenEndpointController, type: :controller, suppres } expect(response.code).to eq("200") end + + it "refresh returns 200 on success" do + post :create, params: { + grant_type: "refresh_token", + refresh_token: auth.refresh_token, + client_id: auth.o_auth_application.client_id, + client_secret: auth.o_auth_application.client_secret + } + expect(response.code).to eq("200") + puts response.body + end end end diff --git a/spec/controllers/api/openid_connect/user_applications_spec.rb b/spec/controllers/api/openid_connect/user_applications_spec.rb index ec119eb70..d9edab4b7 100644 --- a/spec/controllers/api/openid_connect/user_applications_spec.rb +++ b/spec/controllers/api/openid_connect/user_applications_spec.rb @@ -4,7 +4,7 @@ describe Api::OpenidConnect::UserApplicationsController, type: :controller do before do @app = FactoryGirl.create(:o_auth_application_with_xss) @user = FactoryGirl.create :user - FactoryGirl.create :auth_with_read, user: @user, o_auth_application: @app + FactoryGirl.create :auth_with_profile_only, user: @user, o_auth_application: @app sign_in @user, scope: :user end diff --git a/spec/factories.rb b/spec/factories.rb index 8393a5504..2a0920905 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -403,28 +403,59 @@ FactoryGirl.define do redirect_uris %w(http://localhost:3000/) end - factory :auth_with_read, class: Api::OpenidConnect::Authorization do + factory :auth_with_profile_only, class: Api::OpenidConnect::Authorization do o_auth_application user - scopes %w(openid sub aud profile picture nickname name read) + scopes %w[openid profile] after(:build) {|m| m.redirect_uri = m.o_auth_application.redirect_uris[0] } end - factory :auth_with_read_and_ppid, class: Api::OpenidConnect::Authorization do + factory :auth_with_profile_and_ppid, class: Api::OpenidConnect::Authorization do association :o_auth_application, factory: :o_auth_application_with_ppid user - scopes %w(openid sub aud profile picture nickname name read) + scopes %w[openid sub profile picture nickname name] after(:build) {|m| m.redirect_uri = m.o_auth_application.redirect_uris[0] } end - factory :auth_with_read_and_write, class: Api::OpenidConnect::Authorization do + factory :auth_with_all_scopes, class: Api::OpenidConnect::Authorization do o_auth_application association :user, factory: :user_with_aspect - scopes %w(openid sub aud profile picture nickname name read write) + scopes Api::OpenidConnect::Authorization::SCOPES + after(:build) {|m| + m.redirect_uri = m.o_auth_application.redirect_uris[0] + } + end + + factory :auth_with_all_scopes_not_private, class: Api::OpenidConnect::Authorization do + o_auth_application + association :user, factory: :user_with_aspect + scopes %w[openid sub name nickname profile picture gender birthdate locale updated_at contacts:read contacts:modify + conversations email interactions notifications public:read public:modify profile profile:modify tags:read + tags:modify] + after(:build) {|m| + m.redirect_uri = m.o_auth_application.redirect_uris[0] + } + end + + factory :auth_with_read_scopes, class: Api::OpenidConnect::Authorization do + o_auth_application + association :user, factory: :user_with_aspect + scopes %w[openid sub name nickname profile picture gender birthdate locale updated_at contacts:read conversations + email interactions notifications private:read public:read profile tags:read] + after(:build) {|m| + m.redirect_uri = m.o_auth_application.redirect_uris[0] + } + end + + factory :auth_with_read_scopes_not_private, class: Api::OpenidConnect::Authorization do + o_auth_application + association :user, factory: :user_with_aspect + scopes %w[openid sub name nickname profile picture gender birthdate locale updated_at contacts:read conversations + email interactions notifications public:read profile tags:read] after(:build) {|m| m.redirect_uri = m.o_auth_application.redirect_uris[0] } diff --git a/spec/integration/api/aspects_controller_spec.rb b/spec/integration/api/aspects_controller_spec.rb index df86f06a5..d6427caac 100644 --- a/spec/integration/api/aspects_controller_spec.rb +++ b/spec/integration/api/aspects_controller_spec.rb @@ -3,14 +3,31 @@ require "spec_helper" describe Api::V1::AspectsController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } - let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid contacts:read contacts:modify] + ) + } + + let(:auth_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid contacts:read] + ) + } + + let(:auth_profile_only) { + FactoryGirl.create(:auth_with_profile_only) + } + let!(:access_token) { auth.create_access_token.to_s } let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } before do - @aspect1 = auth.user.aspects.where(name: "generic").first - @aspect2 = auth.user.aspects.create(name: "another aspect") + @aspect1 = auth.user.aspects.create(name: "first aspect") + @aspect2 = auth.user.aspects.create(name: "second aspect") end describe "#index" do @@ -29,6 +46,14 @@ describe Api::V1::AspectsController do end end + it "fails if token doesn't have contacts:read" do + get( + api_v1_aspects_path, + params: {access_token: access_token_profile_only} + ) + expect(response.status).to eq(403) + end + it "fails if invalid token" do get( api_v1_aspects_path, @@ -65,6 +90,16 @@ describe Api::V1::AspectsController do end end + context "without contacts:read in token" do + it "fails to return with error" do + get( + api_v1_aspect_path(@aspect2.id), + params: {access_token: access_token_profile_only} + ) + expect(response.status).to eq(403) + end + end + context "when not logged in" do it "fails to return with error" do get( diff --git a/spec/integration/api/comments_controller_spec.rb b/spec/integration/api/comments_controller_spec.rb index 6b4607488..67a8ec5d5 100644 --- a/spec/integration/api/comments_controller_spec.rb +++ b/spec/integration/api/comments_controller_spec.rb @@ -3,8 +3,22 @@ require "spec_helper" describe Api::V1::CommentsController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify private:read private:modify interactions] + ) + } + + let(:auth_public_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify interactions] + ) + } + let!(:access_token) { auth.create_access_token.to_s } + let!(:access_token_public_only) { auth_public_only.create_access_token.to_s } before do @status = alice.post( @@ -24,13 +38,27 @@ describe Api::V1::CommentsController do ) @comment_on_eves_post = comment_service.create(@eves_post.guid, "Comment on eve's post") + + aspect = auth_public_only.user.aspects.create(name: "first aspect") + @private_post = auth_public_only.user.post( + "Post", + status_message: {text: "This is a private status message"}, + public: false, + to: [aspect.id], + type: "Post" + ) + @comment_on_private_post = comment_service(auth_public_only.user).create(@private_post.guid, "Private comment") end describe "#create" do context "valid post ID" do it "succeeds in adding a comment" do comment_text = "This is a comment" - create_comment(@status.guid, comment_text) + post( + api_v1_post_comments_path(post_id: @status.guid), + params: {body: comment_text, access_token: access_token} + ) + expect(response.status).to eq(201) comment = response_body(response) confirm_comment_format(comment, auth.user, comment_text) @@ -39,7 +67,10 @@ describe Api::V1::CommentsController do context "wrong post id" do it "fails at adding a comment" do - create_comment("999_999_999", "This is a comment") + post( + api_v1_post_comments_path(post_id: "999_999_999"), + params: {body: "text", access_token: access_token} + ) expect(response.status).to eq(404) expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) end @@ -48,9 +79,11 @@ describe Api::V1::CommentsController do context "lack of permissions" do it "fails at adding a comment" do alice.blocks.create(person: auth.user.person) - create_comment(@status.guid, "That shouldn't be there because I am ignored by this user") + post( + api_v1_post_comments_path(post_id: @status.guid), + params: {body: "That shouldn't be there because I am ignored by this user", access_token: access_token} + ) expect(response.status).to eq(422) - expect(response.body).to eq(I18n.t("api.endpoint_errors.comments.not_allowed")) end end end @@ -59,8 +92,8 @@ describe Api::V1::CommentsController do before do @comment_text1 = "This is a comment" @comment_text2 = "This is a comment 2" - create_comment(@status.guid, @comment_text1) - create_comment(@status.guid, @comment_text2) + comment_service.create(@status.guid, @comment_text1) + comment_service.create(@status.guid, @comment_text2) end context "valid post ID" do @@ -87,12 +120,23 @@ describe Api::V1::CommentsController do expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) end end + + context "can't see comment on limited post without private:read token" do + it "fails" do + get( + api_v1_post_comments_path(post_id: @private_post.guid), + params: {access_token: access_token_public_only} + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + end + end end describe "#delete" do before do - create_comment(@status.guid, "This is a comment") - @comment_guid = response_body(response)["guid"] + comment = comment_service.create(@status.guid, "This is a comment") + @comment_guid = comment.guid end context "valid comment ID" do @@ -152,8 +196,8 @@ describe Api::V1::CommentsController do end end - context "insufficient permissions (not your comment and not owner)" do - it "fails at deleting comment" do + context "insufficient permissions" do + it "fails at deleting other user's comment on other user's post" do alices_comment = comment_service(alice).create(@status.guid, "Alice's comment") delete( api_v1_post_comment_path( @@ -165,13 +209,24 @@ describe Api::V1::CommentsController do expect(response.status).to eq(403) expect(response.body).to eq(I18n.t("api.endpoint_errors.comments.no_delete")) end + + it "fails at deleting your comment on post without private:modify token" do + delete( + api_v1_post_comment_path( + post_id: @private_post.guid, + id: @comment_on_private_post.guid + ), + params: {access_token: access_token_public_only} + ) + expect(response.status).to eq(404) + end end end describe "#report" do before do - create_comment(@status.guid, "This is a comment") - @comment_guid = response_body(response)["guid"] + comment = comment_service.create(@status.guid, "This is a comment") + @comment_guid = comment.guid end context "valid comment ID" do @@ -227,6 +282,40 @@ describe Api::V1::CommentsController do end end + context "invalid Post ID" do + it "fails at reporting comment" do + post( + api_v1_post_comment_report_path( + post_id: "999_999_999", + comment_id: @comment_guid + ), + params: { + reason: "bad comment", + access_token: access_token + } + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + end + end + + context "lack of private permissions on private post" do + it "fails at reporting comment" do + post( + api_v1_post_comment_report_path( + post_id: @private_post.guid, + comment_id: @comment_on_private_post.guid + ), + params: { + reason: "bad comment", + access_token: access_token_public_only + } + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + end + end + context "mismatched post-to-comment ID" do it "fails at reporting comment" do post( @@ -278,13 +367,6 @@ describe Api::V1::CommentsController do CommentService.new(user) end - def create_comment(post_guid, text) - post( - api_v1_post_comments_path(post_id: post_guid), - params: {body: text, access_token: access_token} - ) - end - def response_body(response) JSON.parse(response.body) end diff --git a/spec/integration/api/contacts_controller_spec.rb b/spec/integration/api/contacts_controller_spec.rb index 42ec41c4f..a85198e21 100644 --- a/spec/integration/api/contacts_controller_spec.rb +++ b/spec/integration/api/contacts_controller_spec.rb @@ -3,13 +3,31 @@ require "spec_helper" describe Api::V1::ContactsController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } - let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid contacts:read contacts:modify] + ) + } + + let(:auth_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid contacts:read] + ) + } + + let(:auth_profile_only) { + FactoryGirl.create(:auth_with_profile_only) + } + let!(:access_token) { auth.create_access_token.to_s } let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } before do - @aspect1 = auth.user.aspects.where(name: "generic").first + @aspect1 = auth.user.aspects.create(name: "generic") + auth.user.share_with(eve.person, @aspect1) @aspect2 = auth.user.aspects.create(name: "another aspect") @eve_aspect = eve.aspects.first end diff --git a/spec/integration/api/conversations_controller_spec.rb b/spec/integration/api/conversations_controller_spec.rb index 442a3c51a..1e80308c1 100644 --- a/spec/integration/api/conversations_controller_spec.rb +++ b/spec/integration/api/conversations_controller_spec.rb @@ -3,12 +3,28 @@ require "spec_helper" describe Api::V1::ConversationsController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid conversations] + ) + } + + let(:auth_participant) { + FactoryGirl.create(:auth_with_all_scopes) + } + + let(:auth_profile_only) { + FactoryGirl.create(:auth_with_profile_only) + } + let!(:access_token) { auth.create_access_token.to_s } - let(:auth_participant) { FactoryGirl.create(:auth_with_read_and_write) } let!(:access_token_participant) { auth_participant.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } + before do + auth.user.aspects.create(name: "first") auth.user.share_with alice.person, auth.user.aspects[0] alice.share_with auth.user.person, alice.aspects[0] auth.user.disconnected_by(eve) diff --git a/spec/integration/api/likes_controller_spec.rb b/spec/integration/api/likes_controller_spec.rb index 720a7925d..439bb82f4 100644 --- a/spec/integration/api/likes_controller_spec.rb +++ b/spec/integration/api/likes_controller_spec.rb @@ -3,8 +3,27 @@ require "spec_helper" describe Api::V1::LikesController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify private:read private:modify interactions] + ) + } + + let(:auth_public_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify interactions] + ) + } + + let(:auth_profile_only) { + FactoryGirl.create(:auth_with_profile_only) + } + let!(:access_token) { auth.create_access_token.to_s } + let!(:access_token_public_only) { auth_public_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } before do @status = auth.user.post( @@ -13,6 +32,15 @@ describe Api::V1::LikesController do public: true, to: "all" ) + + aspect = auth_public_only.user.aspects.create(name: "first aspect") + @private_status = auth_public_only.user.post( + "Post", + status_message: {text: "This is a private status message"}, + public: false, + to: [aspect.id], + type: "Post" + ) end describe "#show" do @@ -54,6 +82,17 @@ describe Api::V1::LikesController do expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) end end + + context "without private:read scope in token" do + it "fails at getting likes" do + get( + api_v1_post_likes_path(post_id: @private_status.guid), + params: {access_token: access_token_public_only} + ) + expect(response.status).to eq(422) + expect(response.body).to eq(I18n.t("api.endpoint_errors.likes.user_not_allowed_to_like")) + end + end end describe "#create" do @@ -87,6 +126,15 @@ describe Api::V1::LikesController do expect(likes.length).to eq(1) expect(likes[0].author.id).to eq(auth.user.person.id) end + + it "fails in liking private post without private:modify" do + post( + api_v1_post_likes_path(post_id: @private_status.guid), + params: {access_token: access_token_public_only} + ) + expect(response.status).to eq(422) + expect(response.body).to eq(I18n.t("api.endpoint_errors.likes.user_not_allowed_to_like")) + end end context "with wrong post id" do @@ -103,10 +151,7 @@ describe Api::V1::LikesController do describe "#delete" do before do - post( - api_v1_post_likes_path(post_id: @status.guid), - params: {access_token: access_token} - ) + like_service.create(@status.guid) end context "with right post id" do @@ -137,6 +182,16 @@ describe Api::V1::LikesController do likes = like_service.find_for_post(@status.guid) expect(likes.length).to eq(0) end + + it "fails at unliking private post without private:modify" do + like_service(auth_public_only.user).create(@private_status.guid) + delete( + api_v1_post_likes_path(post_id: @private_status.guid), + params: {access_token: access_token} + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + end end context "with wrong post id" do diff --git a/spec/integration/api/messages_controller_spec.rb b/spec/integration/api/messages_controller_spec.rb index f1076a29a..74c72d4c8 100644 --- a/spec/integration/api/messages_controller_spec.rb +++ b/spec/integration/api/messages_controller_spec.rb @@ -3,7 +3,13 @@ require "spec_helper" describe Api::V1::MessagesController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid conversations] + ) + } + let!(:access_token) { auth.create_access_token.to_s } before do diff --git a/spec/integration/api/notifications_controller_spec.rb b/spec/integration/api/notifications_controller_spec.rb index 37e147cd0..41ad16945 100644 --- a/spec/integration/api/notifications_controller_spec.rb +++ b/spec/integration/api/notifications_controller_spec.rb @@ -3,10 +3,16 @@ require "spec_helper" describe Api::V1::NotificationsController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } - let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } + let(:auth) { + FactoryGirl.create(:auth_with_all_scopes) + } + + let(:auth_profile_only) { + FactoryGirl.create(:auth_with_profile_only) + } + let!(:access_token) { auth.create_access_token.to_s } - let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } before do @post = auth.user.post( @@ -78,6 +84,14 @@ describe Api::V1::NotificationsController do expect(response.body).to eq(I18n.t("api.endpoint_errors.notifications.cant_process")) end + it "with insufficient credentials" do + get( + api_v1_notifications_path, + params: {access_token: access_token_profile_only} + ) + expect(response.status).to eq(403) + end + it "with improper credentials" do get( api_v1_notifications_path, @@ -128,6 +142,14 @@ describe Api::V1::NotificationsController do expect(response.body).to eq(I18n.t("api.endpoint_errors.notifications.not_found")) end + it "with insufficient credentials" do + get( + api_v1_notification_path(@notification.guid), + params: {access_token: access_token_profile_only} + ) + expect(response.status).to eq(403) + end + it "with improper credentials" do get( api_v1_notification_path(@notification.guid), @@ -175,10 +197,10 @@ describe Api::V1::NotificationsController do expect(response.body).to eq(I18n.t("api.endpoint_errors.notifications.cant_process")) end - it "with read only credentials" do + it "with insufficient credentials" do patch( api_v1_notification_path(@notification.guid), - params: {access_token: access_token_read_only} + params: {access_token: access_token_profile_only} ) expect(response.status).to eq(403) end diff --git a/spec/integration/api/photos_controller_spec.rb b/spec/integration/api/photos_controller_spec.rb index 7b8e6349a..a3cf78466 100644 --- a/spec/integration/api/photos_controller_spec.rb +++ b/spec/integration/api/photos_controller_spec.rb @@ -3,10 +3,43 @@ require "spec_helper" describe Api::V1::PhotosController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } - let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify private:read private:modify] + ) + } + + let(:auth_public_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify] + ) + } + + let(:auth_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read private:read] + ) + } + + let(:auth_public_only_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read] + ) + } + + let(:auth_profile_only) { + FactoryGirl.create(:auth_with_profile_only) + } + let!(:access_token) { auth.create_access_token.to_s } + let!(:access_token_public_only) { auth_public_only.create_access_token.to_s } let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_public_only_read_only) { auth_public_only_read_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } before do alice_private_spec = alice.aspects.create(name: "private aspect") @@ -19,6 +52,17 @@ describe Api::V1::PhotosController do message_data = {status_message: {text: "Post with photos"}, public: true, photos: [@user_photo2.id.to_s]} @status_message = StatusMessageCreationService.new(auth.user).create(message_data) @user_photo2.reload + + shared_spec = auth_public_only_read_only.user.aspects.create(name: "shared aspect") + auth_public_only_read_only.user.share_with(auth_public_only_read_only.user.person, shared_spec) + auth_public_only_read_only.user.share_with(auth_read_only.user.person, shared_spec) + + @shared_photo1 = auth_public_only_read_only.user.post( + :photo, + pending: false, + user_file: File.open(photo_fixture_name), + to: shared_spec.id + ) end describe "#show" do @@ -57,6 +101,15 @@ describe Api::V1::PhotosController do end context "fails" do + it "with other this user's private photo without private:read scope in token" do + get( + api_v1_photo_path(@shared_photo1.guid), + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.photos.not_found")) + end + it "with other user's private photo" do get( api_v1_photo_path(@private_photo1.guid), @@ -98,6 +151,23 @@ describe Api::V1::PhotosController do end end + context "only lists public photos" do + before do + auth_public_only_read_only.user.post(:photo, pending: false, user_file: File.open(photo_fixture_name), + public: true) + end + + it "with correct only public scope token" do + get( + api_v1_photos_path, + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(200) + photos = response_body_data(response) + expect(photos.length).to eq(1) + end + end + context "fails" do it "with invalid access token" do delete( @@ -214,6 +284,14 @@ describe Api::V1::PhotosController do ) expect(response.status).to eq(403) end + + it "with private photo and no private:modify access token" do + post( + api_v1_photos_path, + params: {image: @encoded_photo, access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(403) + end end end @@ -264,6 +342,14 @@ describe Api::V1::PhotosController do ) expect(response.status).to eq(403) end + + it "with private photo and no private:modify token" do + delete( + api_v1_photo_path(@shared_photo1.guid), + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(403) + end end end diff --git a/spec/integration/api/post_interactions_controller_spec.rb b/spec/integration/api/post_interactions_controller_spec.rb index 7c45f3734..86d662904 100644 --- a/spec/integration/api/post_interactions_controller_spec.rb +++ b/spec/integration/api/post_interactions_controller_spec.rb @@ -3,10 +3,27 @@ require "spec_helper" describe Api::V1::PostInteractionsController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify private:read private:modify interactions] + ) + } + + let(:auth_public_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify interactions] + ) + } + + let(:auth_profile_only) { + FactoryGirl.create(:auth_with_profile_only) + } + let!(:access_token) { auth.create_access_token.to_s } - let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } - let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_public_only) { auth_public_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } before do @status = alice.post( @@ -15,6 +32,13 @@ describe Api::V1::PostInteractionsController do public: true, to: "all" ) + + alice_shared_aspect = alice.aspects.create(name: "shared aspect") + alice.share_with(auth_public_only.user.person, alice_shared_aspect) + alice.share_with(auth.user.person, alice_shared_aspect) + alice.share_with(auth_profile_only.user.person, alice_shared_aspect) + + @shared_post = alice.post(:status_message, text: "to aspect only", public: false, to: alice_shared_aspect.id) end describe "#subscribe" do @@ -61,16 +85,26 @@ describe Api::V1::PostInteractionsController do expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) end - it "with read only token" do + it "with insufficient token" do post( api_v1_post_subscribe_path(@status.guid), params: { - access_token: access_token_read_only + access_token: access_token_profile_only } ) expect(response.status).to eq(403) end + it "on private post without private token" do + post( + api_v1_post_subscribe_path(@shared_post.guid), + params: { + access_token: access_token_public_only + } + ) + expect(response.status).to eq(404) + end + it "with invalid token" do post( api_v1_post_subscribe_path(@status.guid), @@ -110,16 +144,26 @@ describe Api::V1::PostInteractionsController do expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) end - it "with read only token" do + it "with insufficient token" do post( api_v1_post_hide_path(@status.guid), params: { - access_token: access_token_read_only + access_token: access_token_profile_only } ) expect(response.status).to eq(403) end + it "on private post without private token" do + post( + api_v1_post_hide_path(@shared_post.guid), + params: { + access_token: access_token_public_only + } + ) + expect(response.status).to eq(404) + end + it "with invalid token" do post( api_v1_post_hide_path(@status.guid), @@ -186,16 +230,26 @@ describe Api::V1::PostInteractionsController do expect(response.status).to eq(404) end - it "with read only token" do + it "with insufficient token" do post( api_v1_post_mute_path(@status.guid), params: { - access_token: access_token_read_only + access_token: access_token_profile_only } ) expect(response.status).to eq(403) end + it "on private post without private token" do + post( + api_v1_post_mute_path(@shared_post.guid), + params: { + access_token: access_token_public_only + } + ) + expect(response.status).to eq(404) + end + it "with invalid token" do post( api_v1_post_mute_path(@status.guid), @@ -268,17 +322,28 @@ describe Api::V1::PostInteractionsController do expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.cant_report")) end - it "with read only token" do + it "with insufficient token" do post( api_v1_post_report_path(@status.guid), params: { reason: "My reason", - access_token: access_token_read_only + access_token: access_token_profile_only } ) expect(response.status).to eq(403) end + it "on private post without private token" do + post( + api_v1_post_report_path(@shared_post.guid), + params: { + reason: "My reason", + access_token: access_token_public_only + } + ) + expect(response.status).to eq(404) + end + it "with invalid token" do post( api_v1_post_report_path(@status.guid), @@ -356,17 +421,29 @@ describe Api::V1::PostInteractionsController do expect(response.status).to eq(404) expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) end - it "with read only token" do + + it "with insufficient token" do post( api_v1_post_vote_path(@poll_post.guid), params: { poll_answer_id: @poll_answer.id, - access_token: access_token_read_only + access_token: access_token_profile_only } ) expect(response.status).to eq(403) end + it "on private post without private token" do + post( + api_v1_post_vote_path(@shared_post.guid), + params: { + poll_answer_id: @poll_answer.id, + access_token: access_token_public_only + } + ) + expect(response.status).to eq(404) + end + it "with invalid token" do post( api_v1_post_vote_path(@poll_post.guid), diff --git a/spec/integration/api/posts_controller_spec.rb b/spec/integration/api/posts_controller_spec.rb index 19562d42b..61b96ecc7 100644 --- a/spec/integration/api/posts_controller_spec.rb +++ b/spec/integration/api/posts_controller_spec.rb @@ -3,16 +3,41 @@ require "spec_helper" describe Api::V1::PostsController do - let!(:auth_with_read) { FactoryGirl.create(:auth_with_read) } - let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify private:read private:modify] + ) + } - let(:auth_with_read_and_write) { - FactoryGirl.create(:auth_with_read_and_write) + let(:auth_public_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify] + ) } - let!(:access_token_with_read_and_write) { - auth_with_read_and_write.create_access_token.to_s + + let(:auth_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read private:read] + ) } + let(:auth_public_only_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read] + ) + } + + let(:auth_profile_only) { FactoryGirl.create(:auth_with_profile_only) } + let!(:access_token) { auth.create_access_token.to_s } + let!(:access_token_public_only) { auth_public_only.create_access_token.to_s } + let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_public_only_read_only) { auth_public_only_read_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } + let(:alice_aspect) { alice.aspects.first } let(:alice_photo1) { @@ -36,87 +61,12 @@ describe Api::V1::PostsController do ) end - context "when mark notifications is omitted" do - # TODO: Determine if this needs to be added back to the spec or if this is just in the notifications services - xit "shows attempts to show the info and mark the user notifications" do - @status = auth_with_read.user.post( - :status_message, - text: "hello @{bob Testing ; bob@example.com}", - public: true, - to: "all" - ) - get( - api_v1_post_path(@status.id), - params: {access_token: access_token_with_read} - ) - expect(response.status).to eq(200) - post = response_body(response) - expect(post["post_type"]).to eq("StatusMessage") - expect(post["public"]).to eq(true) - expect(post["author"]["id"]).to eq(auth_with_read.user.person.id) - expect(post["interactions"]["comments_count"]).to eq(0) - - mention_ids = Mention.where( - mentions_container_id: @status.id, - mentions_container_type: "Post", - person_id: bob.person.id - ).ids - Notification.where( - recipient_id: bob.person.id, - target_type: "Mention", - target_id: mention_ids, - unread: true - ) - # expect(notifications.length).to eq(0) - end - end - - context "when mark notifications is false" do - # TODO: Determine if this needs to be added back to the spec or if this is just in the notifications services - xit "shows attempts to show the info" do - @status = auth_with_read.user.post( - :status_message, - text: "hello @{bob ; bob@example.com}", - public: true, - to: "all" - ) - - get( - api_v1_post_path(@status.id), - params: { - access_token: access_token_with_read, - mark_notifications: "false" - } - ) - expect(response.status).to eq(200) - post = response_body(response) - - expect(post["post_type"]).to eq("StatusMessage") - expect(post["public"]).to eq(true) - expect(post["author"]["id"]).to eq(auth_with_read.user.person.id) - expect(post["interactions"]["comments_count"]).to eq(0) - - mention_ids = Mention.where( - mentions_container_id: @status.id, - mentions_container_type: "Post", - person_id: bob.person.id - ).ids - Notification.where( - recipient_id: bob.person.id, - target_type: "Mention", - target_id: mention_ids, - unread: true - ) - # expect(notifications.length).to eq(1) - end - end - context "access simple by post ID" do it "gets post" do get( - api_v1_post_path(@status.id), + api_v1_post_path(@status.guid), params: { - access_token: access_token_with_read + access_token: access_token } ) expect(response.status).to eq(200) @@ -136,9 +86,9 @@ describe Api::V1::PostsController do status_message = StatusMessageCreationService.new(alice).create(merged_params) get( - api_v1_post_path(status_message.id), + api_v1_post_path(status_message.guid), params: { - access_token: access_token_with_read + access_token: access_token } ) expect(response.status).to eq(200) @@ -151,9 +101,9 @@ describe Api::V1::PostsController do it "gets post" do reshare_post = FactoryGirl.create(:reshare, root: @status, author: bob.person) get( - api_v1_post_path(reshare_post.id), + api_v1_post_path(reshare_post.guid), params: { - access_token: access_token_with_read + access_token: access_token } ) expect(response.status).to eq(200) @@ -166,9 +116,9 @@ describe Api::V1::PostsController do it "fails to get post" do private_post = alice.post(:status_message, text: "to aspect only", public: false, to: alice.aspects.first.id) get( - api_v1_post_path(private_post.id), + api_v1_post_path(private_post.guid), params: { - access_token: access_token_with_read + access_token: access_token } ) expect(response.status).to eq(404) @@ -176,12 +126,38 @@ describe Api::V1::PostsController do end end + context "access private post to reader without private:read scope in token" do + it "fails to get post" do + alice_shared_aspect = alice.aspects.create(name: "shared aspect") + alice.share_with(auth_public_only_read_only.user.person, alice_shared_aspect) + alice.share_with(auth_read_only.user.person, alice_shared_aspect) + + shared_post = alice.post(:status_message, text: "to aspect only", public: false, to: alice_shared_aspect.id) + get( + api_v1_post_path(shared_post.guid), + params: { + access_token: access_token_public_only_read_only + } + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + + get( + api_v1_post_path(shared_post.guid), + params: { + access_token: access_token_read_only + } + ) + expect(response.status).to eq(200) + end + end + context "access post with invalid id" do it "fails to get post" do get( api_v1_post_path("999_999_999"), params: { - access_token: access_token_with_read + access_token: access_token } ) expect(response.status).to eq(404) @@ -192,11 +168,11 @@ describe Api::V1::PostsController do describe "#create" do let(:user_photo1) { - auth_with_read_and_write.user.build_post(:photo, pending: true, + auth.user.build_post(:photo, pending: true, user_file: File.open(photo_fixture_name), to: "all").tap(&:save!) } let(:user_photo2) { - auth_with_read_and_write.user.build_post(:photo, pending: true, + auth.user.build_post(:photo, pending: true, user_file: File.open(photo_fixture_name), to: "all").tap(&:save!) } @@ -205,7 +181,7 @@ describe Api::V1::PostsController do context "when given read-write access token" do it "creates a public post" do - post_for_ref_only = auth_with_read_and_write.user.post( + post_for_ref_only = auth.user.post( :status_message, text: "Hello this is a public post!", public: true, @@ -215,36 +191,51 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: "Hello this is a public post!", public: true } ) expect(response.status).to eq(200) post = response_body(response) - confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only) + confirm_post_format(post, auth.user, post_for_ref_only) end it "or creates a private post" do - aspect = Aspect.find_by(user_id: auth_with_read_and_write.user.id) - post_for_ref_only = auth_with_read_and_write.user.post( + aspect = auth.user.aspects.create(name: "new aspect") + post_for_ref_only = auth.user.post( :status_message, text: "Hello this is a private post!", - aspect_ids: [aspect[:id]] + aspect_ids: [aspect.id] ) post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: "Hello this is a private post!", public: false, - aspects: [aspect[:id]] + aspects: [aspect.id] } ) post = response_body(response) expect(response.status).to eq(200) - confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only) + confirm_post_format(post, auth.user, post_for_ref_only) + end + + it "doesn't creates a private post without private:modify scope in token" do + aspect = auth.user.aspects.create(name: "new aspect") + post( + api_v1_posts_path, + params: { + access_token: access_token_public_only, + body: "Hello this is a private post!", + public: false, + aspects: [aspect.id] + } + ) + + expect(response.status).to eq(422) end end @@ -253,12 +244,12 @@ describe Api::V1::PostsController do message_text = "Post with photos" base_params = {status_message: {text: message_text}, public: true} merged_params = base_params.merge(photos: user_photo_ids) - post_for_ref_only = StatusMessageCreationService.new(auth_with_read_and_write.user).create(merged_params) + post_for_ref_only = StatusMessageCreationService.new(auth.user).create(merged_params) post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: true, photos: user_photo_guids @@ -266,7 +257,7 @@ describe Api::V1::PostsController do ) expect(response.status).to eq(200) post = response_body(response) - confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only) + confirm_post_format(post, auth.user, post_for_ref_only) end it "fails to add other's photos" do @@ -275,7 +266,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: true, photos: alice_photo_guids @@ -292,7 +283,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: true, photos: ["999_999_999"] @@ -307,12 +298,12 @@ describe Api::V1::PostsController do poll_params = {poll_question: "something?", poll_answers: %w[yes no maybe]} base_params = {status_message: {text: message_text}, public: true} merged_params = base_params.merge(poll_params) - post_for_ref_only = StatusMessageCreationService.new(auth_with_read_and_write.user).create(merged_params) + post_for_ref_only = StatusMessageCreationService.new(auth.user).create(merged_params) post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: true, poll: { @@ -323,7 +314,7 @@ describe Api::V1::PostsController do ) post = response_body(response) expect(response.status).to eq(200) - confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only) + confirm_post_format(post, auth.user, post_for_ref_only) end it "fails poll with no answers" do @@ -331,7 +322,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: true, poll: { @@ -349,7 +340,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: true, poll: { @@ -366,7 +357,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: "", public: true, poll: { @@ -384,12 +375,12 @@ describe Api::V1::PostsController do base_params = {status_message: {text: message_text}, public: true} location_params = {location_address: "somewhere", location_coords: "1,2"} merged_params = base_params.merge(location_params) - post_for_ref_only = StatusMessageCreationService.new(auth_with_read_and_write.user).create(merged_params) + post_for_ref_only = StatusMessageCreationService.new(auth.user).create(merged_params) post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: true, location: { @@ -401,12 +392,12 @@ describe Api::V1::PostsController do ) post = response_body(response) expect(response.status).to eq(200) - confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only) + confirm_post_format(post, auth.user, post_for_ref_only) end it "creates with mentions" do message_text = "hello @{#{alice.diaspora_handle}} from Bob!" - post_for_ref_only = auth_with_read_and_write.user.post( + post_for_ref_only = auth.user.post( :status_message, text: message_text, public: true @@ -415,14 +406,14 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: true } ) post = response_body(response) expect(response.status).to eq(200) - confirm_post_format(post, auth_with_read_and_write.user, post_for_ref_only, [alice]) + confirm_post_format(post, auth.user, post_for_ref_only, [alice]) end end @@ -432,7 +423,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: true } @@ -459,7 +450,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, public: true } ) @@ -472,7 +463,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text } ) @@ -485,7 +476,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: false } @@ -499,7 +490,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, public: false, aspects: ["-1"] @@ -510,14 +501,15 @@ describe Api::V1::PostsController do end it "fails when no public field but aspects" do - aspect = Aspect.find_by(user_id: auth_with_read_and_write.user.id) + aspect = auth.user.aspects.create(name: "new aspect") + auth.user.share_with(alice.person, aspect) message_text = "hello @{#{alice.diaspora_handle}} from Bob!" post( api_v1_posts_path, params: { - access_token: access_token_with_read_and_write, + access_token: access_token, body: message_text, - aspects: [aspect[:id]] + aspects: [aspect.id] } ) expect(response.status).to eq(422) @@ -530,7 +522,7 @@ describe Api::V1::PostsController do post( api_v1_posts_path, params: { - access_token: access_token_with_read, + access_token: access_token_read_only, status_message: {text: "Hello this is a post!"}, public: true } @@ -543,15 +535,15 @@ describe Api::V1::PostsController do describe "#destroy" do context "when given read-write access token" do it "attempts to destroy the post" do - @status = auth_with_read_and_write.user.post( + @status = auth.user.post( :status_message, text: "hello", public: true, to: "all" ) delete( - api_v1_post_path(@status.id), - params: {access_token: access_token_with_read_and_write} + api_v1_post_path(@status.guid), + params: {access_token: access_token} ) expect(response.status).to eq(204) end @@ -559,14 +551,31 @@ describe Api::V1::PostsController do context "when given read only access token" do it "doesn't delete the post" do - @status = auth_with_read.user.post( + @status = auth.user.post( :status_message, text: "hello", public: true ) delete( - api_v1_post_path(@status.id), - params: {access_token: access_token_with_read} + api_v1_post_path(@status.guid), + params: {access_token: access_token_read_only} + ) + + expect(response.status).to eq(403) + end + end + + context "when post is private but no private:modify scope in token" do + it "doesn't delete the post" do + aspect = auth_public_only.user.aspects.create(name: "new aspect") + @status = auth_public_only.user.post( + :status_message, + text: "hello", + aspects: [aspect.id] + ) + delete( + api_v1_post_path(@status.guid), + params: {access_token: access_token_public_only} ) expect(response.status).to eq(403) @@ -577,7 +586,7 @@ describe Api::V1::PostsController do it "doesn't delete a post" do delete( api_v1_post_path("999_999_999"), - params: {access_token: access_token_with_read_and_write} + params: {access_token: access_token} ) expect(response.status).to eq(404) expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) @@ -595,7 +604,7 @@ describe Api::V1::PostsController do delete( api_v1_post_path(status.guid), - params: {access_token: access_token_with_read_and_write} + params: {access_token: access_token} ) expect(response.status).to eq(403) expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.failed_delete")) diff --git a/spec/integration/api/reshares_controller_spec.rb b/spec/integration/api/reshares_controller_spec.rb index f02a148fd..d8be142e7 100644 --- a/spec/integration/api/reshares_controller_spec.rb +++ b/spec/integration/api/reshares_controller_spec.rb @@ -3,10 +3,21 @@ require "spec_helper" describe Api::V1::ResharesController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let(:auth) { + FactoryGirl.create(:auth_with_all_scopes) + } + + let(:auth_read_only) { + FactoryGirl.create(:auth_with_read_scopes) + } + + let(:auth_profile_only) { + FactoryGirl.create(:auth_with_profile_only) + } + let!(:access_token) { auth.create_access_token.to_s } - let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } before do @user_post = auth.user.post( diff --git a/spec/integration/api/search_controller_spec.rb b/spec/integration/api/search_controller_spec.rb index eaea24f60..883f8e22a 100644 --- a/spec/integration/api/search_controller_spec.rb +++ b/spec/integration/api/search_controller_spec.rb @@ -3,10 +3,30 @@ require "spec_helper" describe Api::V1::SearchController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify private:read private:modify] + ) + } + + let(:auth_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read private:read] + ) + } + + let(:auth_public_only_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read] + ) + } + let!(:access_token) { auth.create_access_token.to_s } - let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_public_only_read_only) { auth_public_only_read_only.create_access_token.to_s } describe "#user_index" do before do @@ -40,7 +60,7 @@ describe Api::V1::SearchController do ) expect(response.status).to eq(200) users = response_body_data(response) - expect(users.length).to eq(14) + expect(users.length).to eq(15) end it "succeeds by name" do @@ -83,6 +103,19 @@ describe Api::V1::SearchController do expect(users.length).to eq(0) end + it "doesn't return hidden accounts who are linked without contacts:read token" do + aspect_to = auth_public_only_read_only.user.aspects.create(name: "shared aspect") + auth_public_only_read_only.user.share_with(@unsearchable_user, aspect_to) + + get( + "/api/v1/search/users", + params: {name_or_handle: "unsearchable@example.org", access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(200) + users = response_body_data(response) + expect(users.length).to eq(0) + end + it "fails if ask for both" do get( "/api/v1/search/users", @@ -123,16 +156,44 @@ describe Api::V1::SearchController do text: "This is Eve's status message #tag2 #tag3", public: true ) + + aspect = eve.aspects.create(name: "shared aspect") + eve.share_with(auth_public_only_read_only.user.person, aspect) + eve.share_with(auth.user.person, aspect) + @eve_private_post = eve.post( + :status_message, + text: "This is Eve's status message #tag2 #tag3", + public: false, + to: aspect.id + ) end it "succeeds by tag" do + get( + "/api/v1/search/posts", + params: {tag: "tag2", access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(200) + posts = response_body_data(response) + expect(posts.length).to eq(2) + end + + it "only returns public posts without private scope" do + get( + "/api/v1/search/posts", + params: {tag: "tag2", access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(200) + posts = response_body_data(response) + expect(posts.length).to eq(2) + get( "/api/v1/search/posts", params: {tag: "tag2", access_token: access_token} ) expect(response.status).to eq(200) posts = response_body_data(response) - expect(posts.length).to eq(2) + expect(posts.length).to eq(3) end it "fails with missing parameters" do diff --git a/spec/integration/api/streams_controller_spec.rb b/spec/integration/api/streams_controller_spec.rb index 9ca4dcf21..32a01056a 100644 --- a/spec/integration/api/streams_controller_spec.rb +++ b/spec/integration/api/streams_controller_spec.rb @@ -3,57 +3,158 @@ require "spec_helper" describe Api::V1::StreamsController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } - let!(:access_token) { auth.create_access_token.to_s } + let(:auth_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read private:read contacts:read tags:read] + ) + } + + let(:auth_public_only_tags) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read tags:read] + ) + } + + let(:auth_public_only_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read] + ) + } + + let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_public_only_tags) { auth_public_only_tags.create_access_token.to_s } + let!(:access_token_public_only_read_only) { auth_public_only_read_only.create_access_token.to_s } before do - @aspect = auth.user.aspects.first - @created_status = auth.user.post(:status_message, text: "This is a status message #test", public: true, to: "all") - auth.user.like!(@created_status) - @status = PostService.new(auth.user).find(@created_status.id) + @aspect = auth_read_only.user.aspects.create(name: "new aspect") + auth_read_only.user.share_with(auth_public_only_read_only.user.person, @aspect) + + @created_status = auth_read_only.user.post( + :status_message, + text: "This is a status message #test @{#{auth_read_only.user.diaspora_handle}}", + public: true + ) + comment_service(auth_read_only.user).create(@created_status.guid, "Comment") + auth_read_only.user.like!(@created_status) + @status = PostService.new(auth_read_only.user).find(@created_status.id) + + @private_post = auth_read_only.user.post( + :status_message, + text: "This is a private status message #test @{#{auth_read_only.user.diaspora_handle}}", + public: false, + to: @aspect.id + ) + comment_service(auth_read_only.user).create(@private_post.guid, "Comment") + auth_read_only.user.like!(@private_post) + + @created_status2 = auth_public_only_read_only.user.post( + :status_message, + text: "This is a status message #test @{#{auth_public_only_read_only.user.diaspora_handle}}", + public: true + ) + auth_public_only_read_only.user.like!(@created_status2) + comment_service(auth_public_only_read_only.user).create(@created_status2.guid, "Comment") + @created_status3 = auth_public_only_read_only.user.post( + :status_message, + text: "This is a status message #test @{#{auth_public_only_read_only.user.diaspora_handle}}", + public: false, + to: "all" + ) + auth_public_only_read_only.user.like!(@created_status3) + comment_service(auth_public_only_read_only.user).create(@created_status3.guid, "Comment") + + add_tag("test", auth_read_only.user) + add_tag("test", auth_public_only_tags.user) end describe "#aspect" do it "contains expected aspect message" do get( api_v1_aspects_stream_path(aspect_ids: JSON.generate([@aspect.id])), - params: {access_token: access_token} + params: {access_token: access_token_read_only} ) expect(response.status).to eq 200 post = response_body_data(response) - expect(post.length).to eq 1 - confirm_post_format(post[0], auth.user, @status) + expect(post.length).to eq 3 + json_post = post.select {|p| p["guid"] == @status.guid }.first + confirm_post_format(json_post, auth_read_only.user, @status) end it "all aspects expected aspect message" do get( api_v1_aspects_stream_path, - params: {access_token: access_token} + params: {access_token: access_token_read_only} ) expect(response.status).to eq 200 post = response_body_data(response) - expect(post.length).to eq 1 - confirm_post_format(post[0], auth.user, @status) + expect(post.length).to eq 3 + json_post = post.select {|p| p["guid"] == @status.guid }.first + confirm_post_format(json_post, auth_read_only.user, @status) end it "does not save to requested aspects to session" do get( api_v1_aspects_stream_path(a_ids: [@aspect.id]), - params: {access_token: access_token} + params: {access_token: access_token_read_only} ) expect(session[:a_ids]).to be_nil end + + it "fails without impromper credentials" do + get( + api_v1_aspects_stream_path(a_ids: [@aspect.id]), + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(403) + end + + it "fails with invalid credentials" do + get( + api_v1_aspects_stream_path(a_ids: [@aspect.id]), + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq(401) + end end describe "#tags" do - it "all tags expected aspect message" do + it "all tags expected" do get( api_v1_followed_tags_stream_path, - params: {access_token: access_token} + params: {access_token: access_token_read_only} ) - expect(response.status).to eq 200 + expect(response.status).to eq(200) post = response_body_data(response) - expect(post.length).to eq 0 + expect(post.length).to eq(3) + end + + it "public posts only tags expected" do + get( + api_v1_followed_tags_stream_path, + params: {access_token: access_token_public_only_tags} + ) + expect(response.status).to eq(200) + post = response_body_data(response) + expect(post.length).to eq(2) + end + + it "fails with improper credentials" do + get( + api_v1_followed_tags_stream_path, + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(403) + end + + it "fails with invalid credentials" do + get( + api_v1_followed_tags_stream_path, + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq(401) end end @@ -61,12 +162,31 @@ describe Api::V1::StreamsController do it "contains activity message" do get( api_v1_activity_stream_path, - params: {access_token: access_token} + params: {access_token: access_token_read_only} ) expect(response.status).to eq 200 post = response_body_data(response) - expect(post.length).to eq 1 - confirm_post_format(post[0], auth.user, @status) + expect(post.length).to eq 2 + json_post = post.select {|p| p["guid"] == @status.guid }.first + confirm_post_format(json_post, auth_read_only.user, @status) + end + + it "public posts only expected" do + get( + api_v1_activity_stream_path, + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(200) + post = response_body_data(response) + expect(post.length).to eq(1) + end + + it "fails with invalid credentials" do + get( + api_v1_activity_stream_path, + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq(401) end end @@ -74,12 +194,31 @@ describe Api::V1::StreamsController do it "contains main message" do get( api_v1_stream_path, - params: {access_token: access_token} + params: {access_token: access_token_read_only} + ) + expect(response.status).to eq 200 + post = response_body_data(response) + expect(post.length).to eq 3 + json_post = post.select {|p| p["guid"] == @status.guid }.first + confirm_post_format(json_post, auth_read_only.user, @status) + end + + it "public posts only expected" do + get( + api_v1_stream_path, + params: {access_token: access_token_public_only_read_only} ) expect(response.status).to eq 200 post = response_body_data(response) expect(post.length).to eq 1 - confirm_post_format(post[0], auth.user, @status) + end + + it "fails with invalid credentials" do + get( + api_v1_stream_path, + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq 401 end end @@ -87,11 +226,29 @@ describe Api::V1::StreamsController do it "contains commented message" do get( api_v1_commented_stream_path, - params: {access_token: access_token} + params: {access_token: access_token_read_only} ) expect(response.status).to eq 200 post = response_body_data(response) - expect(post.length).to eq 0 + expect(post.length).to eq 2 + end + + it "public posts only expected" do + get( + api_v1_commented_stream_path, + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq 200 + post = response_body_data(response) + expect(post.length).to eq 1 + end + + it "fails with invalid credentials" do + get( + api_v1_commented_stream_path, + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq 401 end end @@ -99,11 +256,29 @@ describe Api::V1::StreamsController do it "contains mentions message" do get( api_v1_mentions_stream_path, - params: {access_token: access_token} + params: {access_token: access_token_read_only} ) expect(response.status).to eq 200 post = response_body_data(response) - expect(post.length).to eq 0 + expect(post.length).to eq 2 + end + + it "public posts only expected" do + get( + api_v1_mentions_stream_path, + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq 200 + post = response_body_data(response) + expect(post.length).to eq 1 + end + + it "fails with invalid credentials" do + get( + api_v1_mentions_stream_path, + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq 401 end end @@ -111,12 +286,31 @@ describe Api::V1::StreamsController do it "contains liked message" do get( api_v1_liked_stream_path, - params: {access_token: access_token} + params: {access_token: access_token_read_only} + ) + expect(response.status).to eq 200 + post = response_body_data(response) + expect(post.length).to eq 2 + json_post = post.select {|p| p["guid"] == @status.guid }.first + confirm_post_format(json_post, auth_read_only.user, @status) + end + + it "public posts only expected" do + get( + api_v1_liked_stream_path, + params: {access_token: access_token_public_only_read_only} ) expect(response.status).to eq 200 post = response_body_data(response) expect(post.length).to eq 1 - confirm_post_format(post[0], auth.user, @status) + end + + it "fails with invalid credentials" do + get( + api_v1_liked_stream_path, + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq 401 end end @@ -209,4 +403,15 @@ describe Api::V1::StreamsController do def response_body_data(response) JSON.parse(response.body)["data"] end + + def add_tag(name, user) + tag = ActsAsTaggableOn::Tag.find_or_create_by(name: name) + tag_following = user.tag_followings.new(tag_id: tag.id) + tag_following.save + tag_following + end + + def comment_service(user) + CommentService.new(user) + end end diff --git a/spec/integration/api/tag_followings_controller_spec.rb b/spec/integration/api/tag_followings_controller_spec.rb index 3e6485858..eb26c4257 100755 --- a/spec/integration/api/tag_followings_controller_spec.rb +++ b/spec/integration/api/tag_followings_controller_spec.rb @@ -3,12 +3,29 @@ require "spec_helper" describe Api::V1::TagFollowingsController do - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid tags:read tags:modify] + ) + } + + let(:auth_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid tags:read] + ) + } + + let(:auth_profile_only) { FactoryGirl.create(:auth_with_profile_only) } let!(:access_token) { auth.create_access_token.to_s } + let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } before do @expected_tags = %w[tag1 tag2 tag3] @expected_tags.each {|tag| add_tag(tag, auth.user) } + @expected_tags.each {|tag| add_tag(tag, auth_read_only.user) } @initial_count = @expected_tags.length end @@ -38,7 +55,7 @@ describe Api::V1::TagFollowingsController do params: {access_token: access_token} ) - expect(response.status).to eq(400) + expect(response.status).to eq(422) expect(response.body).to eq(I18n.t("api.endpoint_errors.tags.cant_process")) end end @@ -50,10 +67,28 @@ describe Api::V1::TagFollowingsController do params: {name: "tag3", access_token: access_token} ) - expect(response.status).to eq(400) + expect(response.status).to eq(422) expect(response.body).to eq(I18n.t("api.endpoint_errors.tags.cant_process")) end end + + context "fails with credentials" do + it "insufficient scopes in token" do + post( + api_v1_tag_followings_path, + params: {name: "tag4", access_token: access_token_read_only} + ) + expect(response.status).to eq(403) + end + + it "invalid token" do + post( + api_v1_tag_followings_path, + params: {name: "tag4", access_token: "999_999_999"} + ) + expect(response.status).to eq(401) + end + end end describe "#index" do @@ -61,7 +96,7 @@ describe Api::V1::TagFollowingsController do it "succeeds" do get( api_v1_tag_followings_path, - params: {access_token: access_token} + params: {access_token: access_token_read_only} ) expect(response.status).to eq(200) items = JSON.parse(response.body) @@ -69,6 +104,24 @@ describe Api::V1::TagFollowingsController do @expected_tags.each {|tag| expect(items.find(tag)).to be_truthy } end end + + context "fails with credentials" do + it "insufficient scopes in token" do + get( + api_v1_tag_followings_path, + params: {access_token: access_token_profile_only} + ) + expect(response.status).to eq(403) + end + + it "invalid token" do + get( + api_v1_tag_followings_path, + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq(401) + end + end end describe "#delete" do @@ -107,6 +160,24 @@ describe Api::V1::TagFollowingsController do expect(items.length).to eq(@initial_count) end end + + context "fails with credentials" do + it "insufficient scopes in token" do + delete( + api_v1_tag_following_path("tag1"), + params: {access_token: access_token_read_only} + ) + expect(response.status).to eq(403) + end + + it "invalid token" do + delete( + api_v1_tag_following_path("tag1"), + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq(401) + end + end end private diff --git a/spec/integration/api/user_info_controller_spec.rb b/spec/integration/api/user_info_controller_spec.rb index 334059d9c..dcae99918 100644 --- a/spec/integration/api/user_info_controller_spec.rb +++ b/spec/integration/api/user_info_controller_spec.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true describe Api::OpenidConnect::UserInfoController do - let!(:auth_with_read_and_ppid) { FactoryGirl.create(:auth_with_read_and_ppid) } + let!(:auth_with_read_and_ppid) { + FactoryGirl.create(:auth_with_profile_and_ppid) + } + let!(:access_token_with_read) { auth_with_read_and_ppid.create_access_token.to_s } describe "#show" do diff --git a/spec/integration/api/users_controller_spec.rb b/spec/integration/api/users_controller_spec.rb index 015de2440..e76adfd2a 100644 --- a/spec/integration/api/users_controller_spec.rb +++ b/spec/integration/api/users_controller_spec.rb @@ -5,10 +5,46 @@ require "spec_helper" describe Api::V1::UsersController do include PeopleHelper - let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } - let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } + let(:full_scopes) { + %w[openid public:read public:modify private:read private:modify contacts:read profile profile:modify] + } + + let(:auth) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: full_scopes + ) + } + + let(:auth_public_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read public:modify] + ) + } + + let(:auth_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read private:read contacts:read profile] + ) + } + + let(:auth_public_only_read_only) { + FactoryGirl.create( + :auth_with_profile_only, + scopes: %w[openid public:read] + ) + } + + let(:auth_profile_only) { + FactoryGirl.create(:auth_with_profile_only) + } let!(:access_token) { auth.create_access_token.to_s } + let!(:access_token_public_only) { auth_public_only.create_access_token.to_s } let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + let!(:access_token_public_only_read_only) { auth_public_only_read_only.create_access_token.to_s } + let!(:access_token_profile_only) { auth_profile_only.create_access_token.to_s } describe "#show" do context "Current User" do @@ -49,7 +85,8 @@ describe Api::V1::UsersController do it "succeeds with in Aspect valid user" do alice.profile[:public_details] = true alice.profile.save - auth.user.share_with(alice.person, auth.user.aspects.first) + aspect = auth.user.aspects.create(name: "first") + auth.user.share_with(alice.person, aspect) get( "/api/v1/users/#{alice.guid}", params: {access_token: access_token} @@ -86,12 +123,31 @@ describe Api::V1::UsersController do it "fails if invalid token" do get( - api_v1_user_path(alice.person.guid), + "/api/v1/users/#{alice.guid}", params: {access_token: "999_999_999"} ) expect(response.status).to eq(401) end + it "fails for private profile if don't have contacts:read" do + unsearchable_user = FactoryGirl.create( + :person, + diaspora_handle: "unsearchable@example.org", + profile: FactoryGirl.build( + :profile, + first_name: "Unsearchable", + last_name: "Person", + searchable: false + ) + ) + + get( + "/api/v1/users/#{unsearchable_user.guid}", + params: {access_token: access_token_profile_only} + ) + expect(response.status).to eq(404) + end + it "fails with invalid user GUID" do get( "/api/v1/users/999_999_999", @@ -240,7 +296,8 @@ describe Api::V1::UsersController do contacts = response_body_data(response) expect(contacts.length).to eq(0) - auth.user.share_with(alice.person, auth.user.aspects.first) + aspect = auth.user.aspects.create(name: "first") + auth.user.share_with(alice.person, aspect) get( api_v1_user_contacts_path(auth.user.guid), params: {access_token: access_token} @@ -269,6 +326,14 @@ describe Api::V1::UsersController do expect(response.body).to eq(I18n.t("api.endpoint_errors.users.not_found")) end + it "fails if insufficient scope token" do + get( + api_v1_user_contacts_path(alice.guid), + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(403) + end + it "fails if invalid token" do get( api_v1_user_contacts_path(alice.guid), @@ -284,10 +349,10 @@ describe Api::V1::UsersController do alice.share_with(eve.person, alice_private_spec) alice.share_with(auth.user.person, alice.aspects.first) - auth.user.post(:photo, pending: false, user_file: File.open(photo_fixture_name), to: "all") - auth.user.post(:photo, pending: false, user_file: File.open(photo_fixture_name), to: "all") - @public_photo1 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name), to: "all") - @public_photo2 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name), to: "all") + auth.user.post(:photo, pending: false, user_file: File.open(photo_fixture_name), public: true) + auth.user.post(:photo, pending: false, user_file: File.open(photo_fixture_name), public: true) + @public_photo1 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name), public: true) + @public_photo2 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name), public: true) @shared_photo1 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name), to: alice.aspects.first.id) @private_photo1 = alice.post(:photo, pending: false, user_file: File.open(photo_fixture_name), @@ -309,6 +374,19 @@ describe Api::V1::UsersController do confirm_photos(photos) end + it "returns only public photos of other user without private:read scope in token" do + get( + api_v1_user_photos_path(alice.guid), + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(200) + photos = response_body_data(response) + expect(photos.length).to eq(2) + guids = photos.map {|photo| photo["guid"] } + expect(guids).to include(@public_photo1.guid, @public_photo2.guid) + expect(guids).not_to include(@private_photo1.guid, @shared_photo1.guid) + end + it "returns logged in user's photos" do get( api_v1_user_photos_path(auth.user.guid), @@ -344,10 +422,11 @@ describe Api::V1::UsersController do alice.share_with(eve.person, alice_private_spec) alice.share_with(auth.user.person, alice.aspects.first) - auth.user.post(:status_message, text: "auth user message1", public: true, to: "all") - auth.user.post(:status_message, text: "auth user message2", public: true, to: "all") - @public_post1 = alice.post(:status_message, text: "alice public message1", public: true, to: "all") - @public_post2 = alice.post(:status_message, text: "alice public message2", public: true, to: "all") + auth.user.post(:status_message, text: "auth user message1", public: true) + auth.user.post(:status_message, text: "auth user message2", public: true) + auth.user.post(:status_message, text: "auth user message3", public: false, to: "all") + @public_post1 = alice.post(:status_message, text: "alice public message1", public: true) + @public_post2 = alice.post(:status_message, text: "alice public message2", public: true) @shared_post1 = alice.post(:status_message, text: "alice limited to auth user message", public: false, to: alice.aspects.first.id) @private_post1 = alice.post(:status_message, text: "alice limited hidden from auth user message", @@ -377,6 +456,16 @@ describe Api::V1::UsersController do ) expect(response.status).to eq(200) posts = response_body_data(response) + expect(posts.length).to eq(3) + end + + it "returns public posts only without private:read scope in token" do + get( + api_v1_user_posts_path(auth.user.guid), + params: {access_token: access_token_public_only_read_only} + ) + expect(response.status).to eq(200) + posts = response_body_data(response) expect(posts.length).to eq(2) end end diff --git a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb index a77da83a0..0419e4a8d 100644 --- a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do - let(:auth_with_read) { FactoryGirl.create(:auth_with_read) } + let(:auth_with_read) { FactoryGirl.create(:auth_with_read_scopes) } let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } let!(:expired_access_token) do access_token = auth_with_read.o_auth_access_tokens.create! diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index bccbbf2ae..0283cb0c9 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -64,9 +64,11 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do end it "should not allow a nil code" do - post api_openid_connect_access_tokens_path, params: {grant_type: "authorization_code", - client_id: client.client_id, client_secret: client.client_secret, - redirect_uri: "http://localhost:3000/", code: nil} + post api_openid_connect_access_tokens_path, params: {grant_type: "authorization_code", + client_id: client.client_id, + client_secret: client.client_secret, + redirect_uri: "http://localhost:3000/", + code: nil} expect(JSON.parse(response.body)["error"]).to eq("invalid_request") end end diff --git a/spec/models/api/openid_connect/id_token_spec.rb b/spec/models/api/openid_connect/id_token_spec.rb index 22efb08be..4134007db 100644 --- a/spec/models/api/openid_connect/id_token_spec.rb +++ b/spec/models/api/openid_connect/id_token_spec.rb @@ -2,7 +2,7 @@ describe Api::OpenidConnect::IdToken, type: :model do describe "#to_jwt" do - let(:auth) { FactoryGirl.create(:auth_with_read) } + let(:auth) { FactoryGirl.create(:auth_with_profile_only) } let(:id_token) { Api::OpenidConnect::IdToken.new(auth, "nonce") } describe "decoded data" do diff --git a/spec/services/post_service_spec.rb b/spec/services/post_service_spec.rb index 21fdf7a24..c7c21e83b 100644 --- a/spec/services/post_service_spec.rb +++ b/spec/services/post_service_spec.rb @@ -181,6 +181,13 @@ describe PostService do PostService.new(alice).destroy(post.id) end + it "won't delete private post if explicitly unallowed" do + expect { + PostService.new(alice).destroy(post.id, false) + }.to raise_error Diaspora::NonPublic + expect(StatusMessage.find_by(id: post.id)).not_to be_nil + end + it "will not let you destroy posts visible to you but that you do not own" do expect { PostService.new(bob).destroy(post.id) diff --git a/spec/shared_behaviors/account_migration.rb b/spec/shared_behaviors/account_migration.rb index 7187c44c3..ff836acea 100644 --- a/spec/shared_behaviors/account_migration.rb +++ b/spec/shared_behaviors/account_migration.rb @@ -176,7 +176,7 @@ shared_examples_for "it updates user references" do end it "updates authorization refrences" do - authorization = FactoryGirl.create(:auth_with_read, user: old_user) + authorization = FactoryGirl.create(:auth_with_read_scopes, user: old_user) run_migration expect(authorization.reload.user).to eq(new_user) end