From 13ceee3e9f2daea642ea058d57dfeb1dbd97ae32 Mon Sep 17 00:00:00 2001 From: Austin Burdine Date: Wed, 7 Feb 2018 10:42:46 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Contributor=20Role=20(#948)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refs https://github.com/TryGhost/Ghost/issues/9314 * added save button for contributor * hide tag filter & redirect to posts.index if post is published * update editor controller test to need session service --- ghost/admin/app/controllers/editor.js | 1 + ghost/admin/app/controllers/team/user.js | 2 +- .../admin/app/mixins/current-user-settings.js | 2 +- ghost/admin/app/models/user.js | 6 ++- ghost/admin/app/routes/application.js | 2 +- ghost/admin/app/routes/editor/edit.js | 7 ++- ghost/admin/app/routes/posts.js | 6 ++- ghost/admin/app/routes/team/index.js | 2 +- ghost/admin/app/routes/team/user.js | 6 +-- ghost/admin/app/styles/layouts/editor.css | 5 ++ .../components/gh-post-settings-menu.hbs | 4 +- .../templates/components/gh-token-input.hbs | 1 + .../components/gh-user-list-item.hbs | 2 +- ghost/admin/app/templates/editor.hbs | 18 +++++-- ghost/admin/app/templates/posts-loading.hbs | 2 +- ghost/admin/app/templates/posts.hbs | 6 ++- .../app/templates/team/index-loading.hbs | 2 +- ghost/admin/app/templates/team/index.hbs | 10 ++-- ghost/admin/tests/acceptance/content-test.js | 38 ++++++++++++++ ghost/admin/tests/acceptance/editor-test.js | 50 +++++++++++++++++++ .../tests/acceptance/settings/amp-test.js | 10 ++++ .../tests/acceptance/settings/apps-test.js | 10 ++++ .../settings/code-injection-test.js | 10 ++++ .../tests/acceptance/settings/design-test.js | 10 ++++ .../tests/acceptance/settings/general-test.js | 10 ++++ .../tests/acceptance/settings/labs-test.js | 10 ++++ .../tests/acceptance/settings/slack-test.js | 10 ++++ .../tests/acceptance/settings/tags-test.js | 10 ++++ .../acceptance/settings/unsplash-test.js | 10 ++++ .../tests/acceptance/subscribers-test.js | 12 +++++ ghost/admin/tests/acceptance/team-test.js | 12 +++++ .../tests/unit/controllers/editor-test.js | 1 + .../unit/helpers/gh-user-can-admin-test.js | 2 +- ghost/admin/tests/unit/models/user-test.js | 23 +++++++++ 34 files changed, 286 insertions(+), 26 deletions(-) diff --git a/ghost/admin/app/controllers/editor.js b/ghost/admin/app/controllers/editor.js index 6977961543..be9c877bea 100644 --- a/ghost/admin/app/controllers/editor.js +++ b/ghost/admin/app/controllers/editor.js @@ -81,6 +81,7 @@ export default Controller.extend({ notifications: service(), router: service(), slugGenerator: service(), + session: service(), ui: service(), /* public properties -----------------------------------------------------*/ diff --git a/ghost/admin/app/controllers/team/user.js b/ghost/admin/app/controllers/team/user.js index b3e608d7a3..e2116badcf 100644 --- a/ghost/admin/app/controllers/team/user.js +++ b/ghost/admin/app/controllers/team/user.js @@ -58,7 +58,7 @@ export default Controller.extend({ deleteUserActionIsVisible: computed('currentUser', 'canAssignRoles', 'user', function () { if ((this.get('canAssignRoles') && this.get('isNotOwnProfile') && !this.get('user.isOwner')) || (this.get('currentUser.isEditor') && (this.get('isNotOwnProfile') - || this.get('user.isAuthor')))) { + || this.get('user.isAuthorOrContributor')))) { return true; } }), diff --git a/ghost/admin/app/mixins/current-user-settings.js b/ghost/admin/app/mixins/current-user-settings.js index 7220b871be..a9acd97180 100644 --- a/ghost/admin/app/mixins/current-user-settings.js +++ b/ghost/admin/app/mixins/current-user-settings.js @@ -3,7 +3,7 @@ import Mixin from '@ember/object/mixin'; export default Mixin.create({ transitionAuthor() { return (user) => { - if (user.get('isAuthor')) { + if (user.get('isAuthorOrContributor')) { return this.transitionTo('team.user', user); } diff --git a/ghost/admin/app/models/user.js b/ghost/admin/app/models/user.js index 053e6f7462..d1a8b8ea0a 100644 --- a/ghost/admin/app/models/user.js +++ b/ghost/admin/app/models/user.js @@ -3,7 +3,7 @@ import Model from 'ember-data/model'; import ValidationEngine from 'ghost-admin/mixins/validation-engine'; import attr from 'ember-data/attr'; import {computed} from '@ember/object'; -import {equal} from '@ember/object/computed'; +import {equal, or} from '@ember/object/computed'; import {hasMany} from 'ember-data/relationships'; import {inject as service} from '@ember/service'; import {task} from 'ember-concurrency'; @@ -46,11 +46,15 @@ export default Model.extend(ValidationEngine, { // TODO: Once client-side permissions are in place, // remove the hard role check. + isContributor: equal('role.name', 'Contributor'), isAuthor: equal('role.name', 'Author'), isEditor: equal('role.name', 'Editor'), isAdmin: equal('role.name', 'Administrator'), isOwner: equal('role.name', 'Owner'), + // This is used in enough places that it's useful to throw it here + isAuthorOrContributor: or('isAuthor', 'isContributor'), + isLoggedIn: computed('id', 'session.user.id', function () { return this.get('id') === this.get('session.user.id'); }), diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index bb62ef7b05..997e78e32f 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -119,7 +119,7 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, { loadServerNotifications(isDelayed) { if (this.get('session.isAuthenticated')) { this.get('session.user').then((user) => { - if (!user.get('isAuthor') && !user.get('isEditor')) { + if (!user.get('isAuthorOrContributor') && !user.get('isEditor')) { this.store.findAll('notification', {reload: true}).then((serverNotifications) => { serverNotifications.forEach((notification) => { if (notification.get('top') || notification.get('custom')) { diff --git a/ghost/admin/app/routes/editor/edit.js b/ghost/admin/app/routes/editor/edit.js index 3d2848bb59..4bedc3f3e8 100644 --- a/ghost/admin/app/routes/editor/edit.js +++ b/ghost/admin/app/routes/editor/edit.js @@ -20,7 +20,12 @@ export default AuthenticatedRoute.extend({ this._super(...arguments); return this.get('session.user').then((user) => { - if (user.get('isAuthor') && !post.isAuthoredByUser(user)) { + if (user.get('isAuthorOrContributor') && !post.isAuthoredByUser(user)) { + return this.replaceWith('posts.index'); + } + + // If the post is not a draft and user is contributor, redirect to index + if (user.get('isContributor') && !post.get('isDraft')) { return this.replaceWith('posts.index'); } }); diff --git a/ghost/admin/app/routes/posts.js b/ghost/admin/app/routes/posts.js index 83cc33de55..8bf319c4b3 100644 --- a/ghost/admin/app/routes/posts.js +++ b/ghost/admin/app/routes/posts.js @@ -45,6 +45,10 @@ export default AuthenticatedRoute.extend(InfinityRoute, { if (user.get('isAuthor')) { // authors can only view their own posts filterParams.author = user.get('slug'); + } else if (user.get('isContributor')) { + // Contributors can only view their own draft posts + filterParams.author = user.get('slug'); + queryParams.status = 'draft'; } else if (params.author) { filterParams.author = params.author; } @@ -78,7 +82,7 @@ export default AuthenticatedRoute.extend(InfinityRoute, { } this.get('session.user').then((user) => { - if (!user.get('isAuthor') && !controller._hasLoadedAuthors) { + if (!user.get('isAuthorOrContributor') && !controller._hasLoadedAuthors) { this.get('store').query('user', {limit: 'all'}).then(() => { controller._hasLoadedAuthors = true; }); diff --git a/ghost/admin/app/routes/team/index.js b/ghost/admin/app/routes/team/index.js index d2e9e052df..432d5429cd 100644 --- a/ghost/admin/app/routes/team/index.js +++ b/ghost/admin/app/routes/team/index.js @@ -29,7 +29,7 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, Infinit }; // authors do not have permission to hit the invites or suspended users endpoint - if (!user.get('isAuthor')) { + if (!user.get('isAuthorOrContributor')) { modelPromises.invites = this.store.query('invite', {limit: 'all'}) .then(() => this.store.filter('invite', invite => !invite.get('isNew'))); diff --git a/ghost/admin/app/routes/team/user.js b/ghost/admin/app/routes/team/user.js index c2ddeec988..880aee3025 100644 --- a/ghost/admin/app/routes/team/user.js +++ b/ghost/admin/app/routes/team/user.js @@ -17,12 +17,12 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, { return this.get('session.user').then((currentUser) => { let isOwnProfile = user.get('id') === currentUser.get('id'); - let isAuthor = currentUser.get('isAuthor'); + let isAuthorOrContributor = currentUser.get('isAuthorOrContributor'); let isEditor = currentUser.get('isEditor'); - if (isAuthor && !isOwnProfile) { + if (isAuthorOrContributor && !isOwnProfile) { this.transitionTo('team.user', currentUser); - } else if (isEditor && !isOwnProfile && !user.get('isAuthor')) { + } else if (isEditor && !isOwnProfile && !user.get('isAuthorOrContributor')) { this.transitionTo('team'); } }); diff --git a/ghost/admin/app/styles/layouts/editor.css b/ghost/admin/app/styles/layouts/editor.css index 14c840532c..6d43586241 100644 --- a/ghost/admin/app/styles/layouts/editor.css +++ b/ghost/admin/app/styles/layouts/editor.css @@ -55,6 +55,11 @@ text-align: right; } +.contributor-save-button { + position: relative; + z-index: 1000; +} + .post-settings { position: relative; z-index: 1000; diff --git a/ghost/admin/app/templates/components/gh-post-settings-menu.hbs b/ghost/admin/app/templates/components/gh-post-settings-menu.hbs index a1d90468bf..7bcae76cf4 100644 --- a/ghost/admin/app/templates/components/gh-post-settings-menu.hbs +++ b/ghost/admin/app/templates/components/gh-post-settings-menu.hbs @@ -65,10 +65,12 @@ }} + {{#unless session.user.isContributor}}
{{gh-psm-tags-input post=post triggerId="tag-input"}}
+ {{/unless}} {{#gh-form-group errors=post.errors hasValidated=post.hasValidated property="customExcerpt"}} @@ -83,7 +85,7 @@ {{gh-error-message errors=post.errors property="customExcerpt" data-test-error="custom-excerpt"}} {{/gh-form-group}} - {{#unless session.user.isAuthor}} + {{#unless session.user.isAuthorOrContributor}}
diff --git a/ghost/admin/app/templates/components/gh-token-input.hbs b/ghost/admin/app/templates/components/gh-token-input.hbs index 4103fe457c..bd824b7b39 100644 --- a/ghost/admin/app/templates/components/gh-token-input.hbs +++ b/ghost/admin/app/templates/components/gh-token-input.hbs @@ -44,6 +44,7 @@ triggerComponent=triggerComponent triggerId=triggerId verticalPosition=verticalPosition + data-test-token-input=true as |option term| }} {{#if option.__isSuggestion__}} diff --git a/ghost/admin/app/templates/components/gh-user-list-item.hbs b/ghost/admin/app/templates/components/gh-user-list-item.hbs index 16f091703c..8b498cce52 100644 --- a/ghost/admin/app/templates/components/gh-user-list-item.hbs +++ b/ghost/admin/app/templates/components/gh-user-list-item.hbs @@ -15,7 +15,7 @@ {{#if user.isLocked}} Locked {{/if}} - {{#unless session.user.isAuthor}} + {{#unless session.user.isAuthorOrContributor}} {{#each user.roles as |role|}} {{role.name}} {{/each}} diff --git a/ghost/admin/app/templates/editor.hbs b/ghost/admin/app/templates/editor.hbs index 1d03d87e1b..bf69068a95 100644 --- a/ghost/admin/app/templates/editor.hbs +++ b/ghost/admin/app/templates/editor.hbs @@ -19,11 +19,19 @@ {{/gh-scheduled-post-countdown}}
{{#unless post.isNew}} - {{gh-publishmenu - post=post - saveTask=save - setSaveType=(action "setSaveType") - onOpen=(action "cancelAutosave")}} + {{#if session.user.isContributor}} + {{gh-task-button "Save" + task=save + runningText="Saving" + class="gh-btn gh-btn-blue gh-btn-icon contributor-save-button" + data-test-contributor-save=true}} + {{else}} + {{gh-publishmenu + post=post + saveTask=save + setSaveType=(action "setSaveType") + onOpen=(action "cancelAutosave")}} + {{/if}} {{/unless}}
diff --git a/ghost/admin/app/templates/team/index.hbs b/ghost/admin/app/templates/team/index.hbs index 9e548d23db..ab56648425 100644 --- a/ghost/admin/app/templates/team/index.hbs +++ b/ghost/admin/app/templates/team/index.hbs @@ -2,7 +2,7 @@

Team members

{{!-- Do not show Invite user button to authors --}} - {{#unless session.user.isAuthor}} + {{#unless session.user.isAuthorOrContributor}}
@@ -18,7 +18,7 @@
{{!-- Show invited users to everyone except authors --}} - {{#unless session.user.isAuthor}} + {{#unless session.user.isAuthorOrContributor}} {{#if invites}}
Invited users @@ -75,8 +75,8 @@
Active users
- {{!-- For authors only show their own user --}} - {{#if session.user.isAuthor}} + {{!-- For authors/contributors only show their own user --}} + {{#if session.user.isAuthorOrContributor}} {{#with session.user as |user|}} {{#gh-user-active user=user as |component|}} {{gh-user-list-item user=user component=component}} @@ -100,7 +100,7 @@
{{!-- Don't show if we have no suspended users or logged in as an author --}} - {{#if (and suspendedUsers (not session.user.isAuthor))}} + {{#if (and suspendedUsers (not session.user.isAuthorOrContributor))}}
Suspended users
diff --git a/ghost/admin/tests/acceptance/content-test.js b/ghost/admin/tests/acceptance/content-test.js index 7f76089cab..41727821a8 100644 --- a/ghost/admin/tests/acceptance/content-test.js +++ b/ghost/admin/tests/acceptance/content-test.js @@ -158,4 +158,42 @@ describe('Acceptance: Content', function () { expect(find(`[data-test-post-id="${authorPost.id}"]`), 'author post').to.exist; }); }); + + describe('as contributor', function () { + let contributor, contributorPost; + + beforeEach(function () { + let contributorRole = server.create('role', {name: 'Contributor'}); + contributor = server.create('user', {roles: [contributorRole]}); + let adminRole = server.create('role', {name: 'Administrator'}); + let admin = server.create('user', {roles: [adminRole]}); + + // Create posts + contributorPost = server.create('post', {authorId: contributor.id, status: 'draft', title: 'Contributor Post Draft'}); + server.create('post', {authorId: contributor.id, status: 'published', title: 'Contributor Published Post'}); + server.create('post', {authorId: admin.id, status: 'scheduled', title: 'Admin Post'}); + + return authenticateSession(application); + }); + + it('only fetches the contributor\'s draft posts', async function () { + await visit('/'); + + // Ensure the type, tag, and author selectors don't exist + expect(find('[data-test-type-select]'), 'type selector').to.not.exist; + expect(find('[data-test-tag-select]'), 'tag selector').to.not.exist; + expect(find('[data-test-author-select]'), 'author selector').to.not.exist; + + // Trigger a sort request + await selectChoose('[data-test-order-select]', 'Oldest'); + + // API request includes author filter + let [lastRequest] = server.pretender.handledRequests.slice(-1); + expect(lastRequest.queryParams.filter).to.equal(`author:${contributor.slug}`); + + // only contributor's post is shown + expect(find('[data-test-post-id]').length, 'post count').to.equal(1); + expect(find(`[data-test-post-id="${contributorPost.id}"]`), 'author post').to.exist; + }); + }); }); diff --git a/ghost/admin/tests/acceptance/editor-test.js b/ghost/admin/tests/acceptance/editor-test.js index ec543dd30f..9555ac245a 100644 --- a/ghost/admin/tests/acceptance/editor-test.js +++ b/ghost/admin/tests/acceptance/editor-test.js @@ -28,6 +28,17 @@ describe('Acceptance: Editor', function () { expect(currentURL(), 'currentURL').to.equal('/signin'); }); + it('does not redirect to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + server.create('post'); + + authenticateSession(application); + await visit('/editor/1'); + + expect(currentURL(), 'currentURL').to.equal('/editor/1'); + }); + it('does not redirect to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); @@ -61,6 +72,45 @@ describe('Acceptance: Editor', function () { expect(currentURL()).to.equal('/editor/1'); }); + describe('when logged in as contributor', function () { + beforeEach(function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role]}); + server.loadFixtures('settings'); + + return authenticateSession(application); + }); + + it('renders a save button instead of a publish menu & hides tags input', async function () { + server.createList('post', 2); + + // post id 1 is a draft, checking for draft behaviour now + await visit('/editor/1'); + + expect(currentURL(), 'currentURL').to.equal('/editor/1'); + + // Expect publish menu to not exist + expect( + find('[data-test-publishmenu-trigger]'), + 'publish menu trigger' + ).to.not.exist; + + // Open post settings menu + await click('[data-test-psm-trigger]'); + + // Check to make sure that tags input doesn't exist + expect( + find('[data-test-token-input]'), + 'tags input' + ).to.not.exist; + + // post id 2 is published, we should be redirected to index + await visit('/editor/2'); + + expect(currentURL(), 'currentURL').to.equal('/'); + }); + }); + describe('when logged in', function () { beforeEach(function () { let role = server.create('role', {name: 'Administrator'}); diff --git a/ghost/admin/tests/acceptance/settings/amp-test.js b/ghost/admin/tests/acceptance/settings/amp-test.js index b70939e39f..a51eb6d1d3 100644 --- a/ghost/admin/tests/acceptance/settings/amp-test.js +++ b/ghost/admin/tests/acceptance/settings/amp-test.js @@ -28,6 +28,16 @@ describe('Acceptance: Settings - Apps - AMP', function () { expect(currentURL(), 'currentURL').to.equal('/signin'); }); + it('redirects to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + await visit('/settings/apps/amp'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/acceptance/settings/apps-test.js b/ghost/admin/tests/acceptance/settings/apps-test.js index e98ab0d807..be2cc51148 100644 --- a/ghost/admin/tests/acceptance/settings/apps-test.js +++ b/ghost/admin/tests/acceptance/settings/apps-test.js @@ -27,6 +27,16 @@ describe('Acceptance: Settings - Apps', function () { expect(currentURL(), 'currentURL').to.equal('/signin'); }); + it('redirects to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + await visit('/settings/apps'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/acceptance/settings/code-injection-test.js b/ghost/admin/tests/acceptance/settings/code-injection-test.js index d23463e3f1..33a6f28ba2 100644 --- a/ghost/admin/tests/acceptance/settings/code-injection-test.js +++ b/ghost/admin/tests/acceptance/settings/code-injection-test.js @@ -29,6 +29,16 @@ describe('Acceptance: Settings - Code-Injection', function () { expect(currentURL(), 'currentURL').to.equal('/signin'); }); + it('redirects to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + await visit('/settings/code-injection'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/acceptance/settings/design-test.js b/ghost/admin/tests/acceptance/settings/design-test.js index 5b4bc80a69..ff0cb02b87 100644 --- a/ghost/admin/tests/acceptance/settings/design-test.js +++ b/ghost/admin/tests/acceptance/settings/design-test.js @@ -26,6 +26,16 @@ describe('Acceptance: Settings - Design', function () { expect(currentURL(), 'currentURL').to.equal('/signin'); }); + it('redirects to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + await visit('/settings/design'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/acceptance/settings/general-test.js b/ghost/admin/tests/acceptance/settings/general-test.js index f97da4f825..3b97872622 100644 --- a/ghost/admin/tests/acceptance/settings/general-test.js +++ b/ghost/admin/tests/acceptance/settings/general-test.js @@ -27,6 +27,16 @@ describe('Acceptance: Settings - General', function () { expect(currentURL(), 'currentURL').to.equal('/signin'); }); + it('redirects to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + await visit('/settings/general'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/acceptance/settings/labs-test.js b/ghost/admin/tests/acceptance/settings/labs-test.js index 7a75d80338..3fa6a4e2bb 100644 --- a/ghost/admin/tests/acceptance/settings/labs-test.js +++ b/ghost/admin/tests/acceptance/settings/labs-test.js @@ -25,6 +25,16 @@ describe('Acceptance: Settings - Labs', function () { expect(currentURL(), 'currentURL').to.equal('/signin'); }); + it('redirects to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + await visit('/settings/labs'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/acceptance/settings/slack-test.js b/ghost/admin/tests/acceptance/settings/slack-test.js index def0ec6b36..61f44d6f1a 100644 --- a/ghost/admin/tests/acceptance/settings/slack-test.js +++ b/ghost/admin/tests/acceptance/settings/slack-test.js @@ -24,6 +24,16 @@ describe('Acceptance: Settings - Apps - Slack', function () { expect(currentURL(), 'currentURL').to.equal('/signin'); }); + it('redirects to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + await visit('/settings/apps/slack'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/acceptance/settings/tags-test.js b/ghost/admin/tests/acceptance/settings/tags-test.js index ce155bb1e8..a19a756c0d 100644 --- a/ghost/admin/tests/acceptance/settings/tags-test.js +++ b/ghost/admin/tests/acceptance/settings/tags-test.js @@ -56,6 +56,16 @@ describe('Acceptance: Settings - Tags', function () { expect(currentURL()).to.equal('/signin'); }); + it('redirects to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + await visit('/settings/design'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/acceptance/settings/unsplash-test.js b/ghost/admin/tests/acceptance/settings/unsplash-test.js index fe08068e17..049f92f990 100644 --- a/ghost/admin/tests/acceptance/settings/unsplash-test.js +++ b/ghost/admin/tests/acceptance/settings/unsplash-test.js @@ -23,6 +23,16 @@ describe('Acceptance: Settings - Apps - Unsplash', function () { expect(currentURL(), 'currentURL').to.equal('/signin'); }); + it('redirects to team page when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + authenticateSession(application); + await visit('/settings/apps/unsplash'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects to team page when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/acceptance/subscribers-test.js b/ghost/admin/tests/acceptance/subscribers-test.js index e67d07251b..f0d9d53d0f 100644 --- a/ghost/admin/tests/acceptance/subscribers-test.js +++ b/ghost/admin/tests/acceptance/subscribers-test.js @@ -46,6 +46,18 @@ describe('Acceptance: Subscribers', function () { .to.equal(0); }); + it('redirects contributors to posts', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role]}); + + authenticateSession(application); + await visit('/subscribers'); + + expect(currentURL()).to.equal('/'); + expect(find('.gh-nav-main a:contains("Subscribers")').length, 'sidebar link is visible') + .to.equal(0); + }); + describe('an admin', function () { beforeEach(function () { let role = server.create('role', {name: 'Administrator'}); diff --git a/ghost/admin/tests/acceptance/team-test.js b/ghost/admin/tests/acceptance/team-test.js index 8f27d2cf27..3ac7f4943e 100644 --- a/ghost/admin/tests/acceptance/team-test.js +++ b/ghost/admin/tests/acceptance/team-test.js @@ -27,6 +27,18 @@ describe('Acceptance: Team', function () { expect(currentURL()).to.equal('/signin'); }); + it('redirects correctly when authenticated as contributor', async function () { + let role = server.create('role', {name: 'Contributor'}); + server.create('user', {roles: [role], slug: 'test-user'}); + + server.create('user', {slug: 'no-access'}); + + authenticateSession(application); + await visit('/team/no-access'); + + expect(currentURL(), 'currentURL').to.equal('/team/test-user'); + }); + it('redirects correctly when authenticated as author', async function () { let role = server.create('role', {name: 'Author'}); server.create('user', {roles: [role], slug: 'test-user'}); diff --git a/ghost/admin/tests/unit/controllers/editor-test.js b/ghost/admin/tests/unit/controllers/editor-test.js index 4e73c90181..bd628c6615 100644 --- a/ghost/admin/tests/unit/controllers/editor-test.js +++ b/ghost/admin/tests/unit/controllers/editor-test.js @@ -15,6 +15,7 @@ describe('Unit: Controller: editor', function () { 'service:notifications', // 'service:router', 'service:slugGenerator', + 'service:session', 'service:ui' ] }); diff --git a/ghost/admin/tests/unit/helpers/gh-user-can-admin-test.js b/ghost/admin/tests/unit/helpers/gh-user-can-admin-test.js index 51beaabafe..acc17eeb9c 100644 --- a/ghost/admin/tests/unit/helpers/gh-user-can-admin-test.js +++ b/ghost/admin/tests/unit/helpers/gh-user-can-admin-test.js @@ -37,7 +37,7 @@ describe('Unit: Helper: gh-user-can-admin', function () { }); }); - describe('Editor and Author roles', function () { + describe('Editor, Author & Contributor roles', function () { let user = { get(role) { if (role === 'isOwner') { diff --git a/ghost/admin/tests/unit/models/user-test.js b/ghost/admin/tests/unit/models/user-test.js index 33bc76a85b..60c6b71422 100644 --- a/ghost/admin/tests/unit/models/user-test.js +++ b/ghost/admin/tests/unit/models/user-test.js @@ -61,6 +61,21 @@ describe('Unit: Model: user', function () { expect(model.get('role.name')).to.equal('Editor'); }); + it('isContributor property is correct', function () { + let model = this.subject(); + + run(() => { + let role = this.store().push({data: {id: 1, type: 'role', attributes: {name: 'Contributor'}}}); + model.set('role', role); + }); + expect(model.get('isContributor')).to.be.ok; + expect(model.get('isAuthorOrContributor')).to.be.ok; + expect(model.get('isAuthor')).to.not.be.ok; + expect(model.get('isEditor')).to.not.be.ok; + expect(model.get('isAdmin')).to.not.be.ok; + expect(model.get('isOwner')).to.not.be.ok; + }); + it('isAuthor property is correct', function () { let model = this.subject(); @@ -69,6 +84,8 @@ describe('Unit: Model: user', function () { model.set('role', role); }); expect(model.get('isAuthor')).to.be.ok; + expect(model.get('isContributor')).to.not.be.ok; + expect(model.get('isAuthorOrContributor')).to.be.ok; expect(model.get('isEditor')).to.not.be.ok; expect(model.get('isAdmin')).to.not.be.ok; expect(model.get('isOwner')).to.not.be.ok; @@ -83,6 +100,8 @@ describe('Unit: Model: user', function () { }); expect(model.get('isEditor')).to.be.ok; expect(model.get('isAuthor')).to.not.be.ok; + expect(model.get('isContributor')).to.not.be.ok; + expect(model.get('isAuthorOrContributor')).to.not.be.ok; expect(model.get('isAdmin')).to.not.be.ok; expect(model.get('isOwner')).to.not.be.ok; }); @@ -96,6 +115,8 @@ describe('Unit: Model: user', function () { }); expect(model.get('isAdmin')).to.be.ok; expect(model.get('isAuthor')).to.not.be.ok; + expect(model.get('isContributor')).to.not.be.ok; + expect(model.get('isAuthorOrContributor')).to.not.be.ok; expect(model.get('isEditor')).to.not.be.ok; expect(model.get('isOwner')).to.not.be.ok; }); @@ -109,6 +130,8 @@ describe('Unit: Model: user', function () { }); expect(model.get('isOwner')).to.be.ok; expect(model.get('isAuthor')).to.not.be.ok; + expect(model.get('isContributor')).to.not.be.ok; + expect(model.get('isAuthorOrContributor')).to.not.be.ok; expect(model.get('isAdmin')).to.not.be.ok; expect(model.get('isEditor')).to.not.be.ok; });