diff --git a/app/components/billing/authorization.js b/app/components/billing/authorization.js
index 39519fd4e9..0baa0c7b66 100644
--- a/app/components/billing/authorization.js
+++ b/app/components/billing/authorization.js
@@ -23,7 +23,6 @@ export default Component.extend({
requiresSource: equal('subscription.paymentIntent.status', 'requires_source'),
lastPaymentIntentError: reads('subscription.paymentIntent.last_payment_error'),
retryAuthorizationClientSecret: reads('subscription.paymentIntent.client_secret'),
- hasSubscriptionPermissions: reads('account.hasSubscriptionPermissions'),
notChargeInvoiceSubscription: not('subscription.chargeUnpaidInvoices.lastSuccessful.value'),
freeV2Plan: equal('subscription.plan.startingPrice', 0),
isSubscribed: reads('subscription.isSubscribed'),
@@ -32,6 +31,10 @@ export default Component.extend({
canCancelSubscription: computed('isSubscribed', 'hasSubscriptionPermissions', 'freeV2Plan', 'isTrial', function () {
return this.isSubscribed && this.hasSubscriptionPermissions && !this.freeV2Plan && !this.isTrial;
}),
+
+ hasSubscriptionPermissions: computed('account.hasSubscriptionPermissions', 'account.permissions', function () {
+ return this.account.hasSubscriptionPermissions && (!this.account.isOrganization || this.account.permissions.plan_create);
+ }),
cancelSubscriptionLoading: reads('subscription.cancelSubscription.isRunning'),
isTrial: reads('subscription.plan.isTrial'),
isLoading: or('accounts.fetchSubscriptions.isRunning', 'accounts.fetchV2Subscriptions.isRunning',
diff --git a/app/components/billing/payment-details-tab.js b/app/components/billing/payment-details-tab.js
index 8128a620fb..f612c2db24 100644
--- a/app/components/billing/payment-details-tab.js
+++ b/app/components/billing/payment-details-tab.js
@@ -15,6 +15,8 @@ export default Component.extend({
metrics: service(),
countries,
+
+ model: reads('activeModel'),
states: computed('country', function () {
const { country } = this;
@@ -37,6 +39,12 @@ export default Component.extend({
isV2SubscriptionEmpty: empty('v2subscription'),
isSubscriptionEmpty: empty('v1subscription'),
isSubscriptionsEmpty: and('isSubscriptionEmpty', 'isV2SubscriptionEmpty'),
+ canViewBilling: computed('model', function () {
+ return !this.account.isOrganization || this.account.permissions.billing_view;
+ }),
+ canEditBilling: computed('model', function () {
+ return !this.account.isOrganization || this.account.permissions.billing_update;
+ }),
hasV2Subscription: not('isV2SubscriptionEmpty'),
subscription: computed('v1subscription', 'v2subscription', function () {
return this.isV2SubscriptionEmpty ? this.get('v1subscription') : this.get('v2subscription');
diff --git a/app/components/billing/select-plan.js b/app/components/billing/select-plan.js
index d12358cd82..323f2cfe77 100644
--- a/app/components/billing/select-plan.js
+++ b/app/components/billing/select-plan.js
@@ -31,6 +31,9 @@ export default Component.extend({
return false;
}
}),
+ hasPlanChangePermission: computed('account', function () {
+ return !this.account.isOrganization || this.account.permissions.plan_create;
+ }),
save: task(function* () {
if (this.next.perform) {
diff --git a/app/components/github-apps-repository.js b/app/components/github-apps-repository.js
index dac327d63e..b025fa21a6 100644
--- a/app/components/github-apps-repository.js
+++ b/app/components/github-apps-repository.js
@@ -41,9 +41,18 @@ export default Component.extend({
return this.user && vcsLinks.accessSettingsUrl(this.user.vcsType, { owner: this.user.login });
}),
+ hasActivatePermission: computed('permissions.all', 'repository', function () {
+ let repo = this.repository;
+ let forRepo = (repo.owner.id == this.user.id && repo.ownerType == 'user') ||
+ ((repo.shared || repo.ownerType != 'user') && repo.permissions.activate);
+ return forRepo;
+ }),
+
hasSettingsPermission: computed('permissions.all', 'repository', function () {
let repo = this.repository;
- return this.permissions.hasPushPermission(repo);
+ let forRepo = (repo.owner.id == this.user.id && repo.ownerType == 'user') ||
+ ((repo.shared || repo.ownerType != 'user') && repo.permissions.settings_read);
+ return this.permissions.hasPushPermission(repo) && forRepo;
}),
hasEmailSubscription: computed('repository', 'repository.emailSubscribed', function () {
diff --git a/app/components/log-content.js b/app/components/log-content.js
index c72db2c278..cc103e3dbd 100644
--- a/app/components/log-content.js
+++ b/app/components/log-content.js
@@ -220,12 +220,13 @@ export default Component.extend({
return this.permissions.hasPermission(repo);
}),
- canRemoveLog: computed('job', 'job.canRemoveLog', 'hasPermission', function () {
+ canRemoveLog: computed('job', 'job.canRemoveLog', 'hasPermission', 'currentUser', function () {
let job = this.job;
let canRemoveLog = this.get('job.canRemoveLog');
let hasPermission = this.hasPermission;
+ let access = this.currentUser && this.currentUser.hasPermissionToRepo(this.get('job.repo'), 'log_delete');
if (job) {
- return canRemoveLog && hasPermission;
+ return canRemoveLog && hasPermission && access;
}
}),
@@ -247,7 +248,14 @@ export default Component.extend({
},
toggleLog() {
- this.toggleProperty('logIsVisible');
+ let access = this.currentUser && this.currentUser.hasPermissionToRepo(this.get('job.repo'), 'log_view');
+ if (access) {
+ this.toggleProperty('logIsVisible');
+ } else {
+ if (this.logIsVisible) {
+ this.toggleProperty('logIsVisible');
+ }
+ }
},
toggleRemoveLogModal() {
diff --git a/app/components/profile-nav.js b/app/components/profile-nav.js
index c0e2c13c7d..8ab652f27c 100644
--- a/app/components/profile-nav.js
+++ b/app/components/profile-nav.js
@@ -64,23 +64,47 @@ export default Component.extend({
isOrganization: reads('model.isOrganization'),
hasAdminPermissions: reads('model.permissions.admin'),
+ hasPlanViewPermissions: reads('model.permissions.plan_view'),
+ hasPlanUsagePermissions: reads('model.permissions.plan_usage'),
+ hasPlanCreatePermissions: reads('model.permissions.plan_create'),
+ hasBillingViewPermissions: reads('model.permissions.billing_view'),
+ hasInvoicesViewPermissions: reads('model.permissions.plan_invoices'),
+ hasSettingsReadPermissions: reads('model.permissions.settings_read'),
+ hasSettingsCreatePermissions: reads('model.permissions.settings_create'),
isOrganizationAdmin: and('isOrganization', 'hasAdminPermissions'),
- showOrganizationSettings: and('isOrganizationAdmin', 'isProVersion'),
-
- showSubscriptionTab: computed('features.enterpriseVersion', 'model.isAssembla', 'model.isUser', function () {
- const isAssemblaUser = this.model.isUser && this.model.isAssembla;
- const isEnterprise = this.features.get('enterpriseVersion');
- return !isEnterprise && !isAssemblaUser && !!billingEndpoint;
+ showOrganizationSettings: computed('isOrganizationAdmin', 'isProVersion', 'hasSettingsCreatePermissions', function () {
+ const forOrganization = !this.isOrganization || this.hasSettingsCreatePermissions;
+ return this.isProVersion && forOrganization;
}),
- showPaymentDetailsTab: computed('showSubscriptionTab', 'isOrganization', 'isOrganizationAdmin', 'model.isNotGithubOrManual', function () {
- if (this.isOrganization) {
- return this.showSubscriptionTab && this.isOrganizationAdmin && this.model.get('isNotGithubOrManual');
- } else {
- return this.showSubscriptionTab && this.model.get('isNotGithubOrManual');
- }
+
+ showSubscriptionTab: computed('features.enterpriseVersion', 'hasPlanViewPermissions',
+ 'hasPlanCreatePermissions', 'model.isAssembla', 'model.isUser',
+ 'isOrganization', function () {
+ const forOrganization = !this.isOrganization ||
+ ((this.model.hasSubscription || this.model.hasV2Subscription) && !!this.hasPlanViewPermissions) ||
+ !!this.hasPlanCreatePermissions;
+
+ const isAssemblaUser = this.model.isUser && this.model.isAssembla;
+ const isEnterprise = this.features.get('enterpriseVersion');
+ return !isEnterprise && !isAssemblaUser && !!billingEndpoint && !!forOrganization;
+ }),
+ showPaymentDetailsTab: computed('showSubscriptionTab', 'isOrganization', 'isOrganizationAdmin',
+ 'hasBillingViewPermissions', 'hasInvoicesViewPermissions', 'model.isNotGithubOrManual', function () {
+ if (this.isOrganization) {
+ const forOrganization = !this.isOrganization || this.hasBillingViewPermissions || this.hasInvoicesViewPermissions;
+
+ return this.showSubscriptionTab && this.model.get('isNotGithubOrManual') && (this.isOrganizationAdmin || forOrganization);
+ } else {
+ return this.showSubscriptionTab && this.model.get('isNotGithubOrManual');
+ }
+ }),
+ showPlanUsageTab: computed('showSubscriptionTab', 'model.hasCredits', 'hasPlanUsagePermissions', function () {
+ const forOrganization = !this.isOrganization || this.hasPlanUsagePermissions;
+ return this.showSubscriptionTab && this.model.hasCredits && forOrganization;
}),
- showPlanUsageTab: and('showSubscriptionTab', 'model.hasCredits'),
- usersUsage: computed('account.allowance.userUsage', 'addonUsage', function () {
+
+ usersUsage: computed('account.allowance.userUsage', 'addonUsage', 'hasPlanUsagePermissions', function () {
+ // const forOrganization = !this.isOrganization || this.hasPlanUsagePermissions;
const userUsage = this.model.allowance.get('userUsage');
if (userUsage === undefined) {
return true;
diff --git a/app/components/repo-actions.js b/app/components/repo-actions.js
index 586e45c753..2a8035111d 100644
--- a/app/components/repo-actions.js
+++ b/app/components/repo-actions.js
@@ -38,6 +38,7 @@ export default Component.extend({
userHasPermissionForRepo: computed('repo.id', 'user', 'user.permissions.[]', function () {
let repo = this.repo;
let user = this.user;
+
if (user && repo) {
return user.hasAccessToRepo(repo);
}
@@ -58,6 +59,27 @@ export default Component.extend({
return user.hasPushAccessToRepo(repo);
}
}),
+ userHasCancelPermissionForRepo: computed('repo.id', 'user', function () {
+ let repo = this.repo;
+ let user = this.user;
+ if (user && repo) {
+ return user.hasPermissionToRepo(repo, 'build_cancel');
+ }
+ }),
+ userHasRestartPermissionForRepo: computed('repo.id', 'user', function () {
+ let repo = this.repo;
+ let user = this.user;
+ if (user && repo) {
+ return user.hasPermissionToRepo(repo, 'build_restart');
+ }
+ }),
+ userHasDebugPermissionForRepo: computed('repo.id', 'user', function () {
+ let repo = this.repo;
+ let user = this.user;
+ if (user && repo) {
+ return user.hasPermissionToRepo(repo, 'build_debug');
+ }
+ }),
canOwnerBuild: reads('repo.canOwnerBuild'),
ownerRoMode: reads('repo.owner.ro_mode'),
@@ -68,9 +90,9 @@ export default Component.extend({
showPriority: true,
showPrioritizeBuildModal: false,
- canCancel: and('userHasPullPermissionForRepo', 'item.canCancel'),
- canRestart: and('userHasPullPermissionForRepo', 'item.canRestart'),
- canDebug: and('userHasPushPermissionForRepo', 'item.canDebug'),
+ canCancel: and('userHasCancelPermissionForRepo', 'item.canCancel'),
+ canRestart: and('userHasRestartPermissionForRepo', 'item.canRestart'),
+ canDebug: and('userHasDebugPermissionForRepo', 'item.canDebug'),
isHighPriority: or('item.priority', 'item.build.priority'),
isNotAlreadyHighPriority: not('isHighPriority'),
hasPrioritizePermission: or('item.permissions.prioritize', 'item.build.permissions.prioritize'),
diff --git a/app/components/repo-show-tools.js b/app/components/repo-show-tools.js
index 9272a08d4c..46dfe30660 100644
--- a/app/components/repo-show-tools.js
+++ b/app/components/repo-show-tools.js
@@ -27,12 +27,16 @@ export default Component.extend({
displaySettingsLink: computed('permissions.all', 'repo', function () {
let repo = this.repo;
- return this.permissions.hasPushPermission(repo);
+ const forRepo = repo.permissions.settings_read;
+
+ return this.permissions.hasPushPermission(repo) && forRepo;
}),
displayCachesLink: computed('permissions.all', 'repo', function () {
let repo = this.repo;
- return this.permissions.hasPushPermission(repo) && config.endpoints.caches;
+ const forRepo = repo.permissions.cache_view;
+
+ return this.permissions.hasPushPermission(repo) && config.endpoints.caches && forRepo;
}),
displayStatusImages: computed('permissions.all', 'repo', function () {
@@ -49,11 +53,12 @@ export default Component.extend({
let canTriggerBuild = this.get('repo.permissions.create_request');
let enterprise = this.get('features.enterpriseVersion');
let pro = this.get('features.proVersion');
+ const forRepo = this.repo.permissions.build_create;
if (enterprise || pro) {
- return canTriggerBuild;
+ return canTriggerBuild && forRepo;
}
- return canTriggerBuild && migrationStatus !== 'migrated';
+ return canTriggerBuild && migrationStatus !== 'migrated' && forRepo;
}
),
diff --git a/app/models/build.js b/app/models/build.js
index b52e616ca3..56ab1bd34a 100644
--- a/app/models/build.js
+++ b/app/models/build.js
@@ -134,7 +134,9 @@ export default Model.extend(DurationCalculations, {
return !isEmpty(jobs.filterBy('canCancel'));
}),
- canRestart: alias('isFinished'),
+ canRestart: computed('isFinished', function () {
+ return this.isFinished;
+ }),
cancel() {
const url = `/build/${this.id}/cancel`;
diff --git a/app/models/job.js b/app/models/job.js
index 4d07b6fe1e..9163139b90 100644
--- a/app/models/job.js
+++ b/app/models/job.js
@@ -2,7 +2,7 @@
import Model, { attr, belongsTo } from '@ember-data/model';
import { observer, computed } from '@ember/object';
-import { alias, and, equal, not, reads } from '@ember/object/computed';
+import { alias, and, equal, reads } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import { isEqual } from '@ember/utils';
import { getOwner } from '@ember/application';
@@ -154,11 +154,13 @@ export default Model.extend(DurationCalculations, DurationAttributes, {
canCancel: computed('isFinished', 'state', function () {
let isFinished = this.isFinished;
let state = this.state;
- // not(isFinished) is insufficient since it will be true when state is undefined.
return !isFinished && !!state;
}),
- canRestart: alias('isFinished'),
+ canRestart: computed('isFinished', function () {
+ let isFinished = this.isFinished;
+ return isFinished;
+ }),
canDebug: and('isFinished', 'repo.private'),
cancel() {
@@ -234,7 +236,10 @@ export default Model.extend(DurationCalculations, DurationAttributes, {
}
}),
- canRemoveLog: not('log.removed'),
+ canRemoveLog: computed('log.removed', function () {
+ let removed = !!this.log.removed;
+ return !removed;
+ }),
slug: computed('repo.slug', 'number', function () {
let slug = this.get('repo.slug');
diff --git a/app/models/repo.js b/app/models/repo.js
index 41bb6d98f2..42b2ca6c3e 100644
--- a/app/models/repo.js
+++ b/app/models/repo.js
@@ -211,6 +211,9 @@ const Repo = VcsEntity.extend({
fetchSettings: task(function* () {
if (!this.auth.signedIn) return {};
+
+ const hasPermissions = this.permissions.settings_read;
+ if (hasPermissions === false) return {};
try {
const response = yield this.api.get(`/repo/${this.id}/settings`);
return this._convertV3SettingsToV2(response.settings);
diff --git a/app/models/user.js b/app/models/user.js
index 91c7f31402..fb80baff66 100644
--- a/app/models/user.js
+++ b/app/models/user.js
@@ -73,6 +73,13 @@ export default Owner.extend({
}
},
+ hasPermissionToRepo(repo, permission) {
+ let permissions = repo.get ? repo.get('permissions') : null;
+ if (permissions) {
+ return permissions[permission] || false;
+ }
+ },
+
sync(isOrganization) {
this.set('isSyncing', true);
this.set('applyFilterRepos', !isOrganization);
diff --git a/app/routes/caches.js b/app/routes/caches.js
index 7e08519180..ca0a45d433 100644
--- a/app/routes/caches.js
+++ b/app/routes/caches.js
@@ -11,6 +11,15 @@ export default TravisRoute.extend({
return this.controllerFor('repo').activate('caches');
},
+ beforeModel() {
+ const repo = this.modelFor('repo');
+ if (!repo.permissions.cache_view) {
+ this.transitionTo('repo.index');
+ this.flashes.error('Your permissions are insufficient to access this repository\'s cache');
+ }
+ },
+
+
model() {
const repo = this.modelFor('repo');
const url = `/repo/${repo.get('id')}/caches`;
diff --git a/app/routes/organization/billing.js b/app/routes/organization/billing.js
index a28a8682bb..30a1ee308a 100644
--- a/app/routes/organization/billing.js
+++ b/app/routes/organization/billing.js
@@ -5,7 +5,7 @@ import AccountBillingMixin from 'travis/mixins/route/account/billing';
export default TravisRoute.extend(AccountBillingMixin, {
model() {
const organization = this.modelFor('organization');
- if (organization.permissions && organization.permissions.admin !== true) {
+ if (organization.permissions && organization.permissions.plan_view !== true) {
this.transitionTo('organization.repositories', organization);
}
return hash({
diff --git a/app/routes/organization/plan_usage.js b/app/routes/organization/plan_usage.js
index fcd1a4b612..9d99faadc2 100644
--- a/app/routes/organization/plan_usage.js
+++ b/app/routes/organization/plan_usage.js
@@ -5,7 +5,7 @@ import { hash } from 'rsvp';
export default TravisRoute.extend(AccountPlanUsageMixin, {
model() {
const organization = this.modelFor('organization');
- if (organization.permissions && organization.permissions.admin !== true) {
+ if (organization.permissions && organization.permissions.plan_usage !== true) {
this.transitionTo('organization.repositories', organization);
}
return hash({
diff --git a/app/routes/organization/settings.js b/app/routes/organization/settings.js
index 6083988bcd..c064bb0748 100644
--- a/app/routes/organization/settings.js
+++ b/app/routes/organization/settings.js
@@ -13,9 +13,6 @@ export default TravisRoute.extend({
model() {
const organization = this.modelFor('organization');
- if (organization.permissions.admin !== true) {
- this.transitionTo('organization.repositories', organization);
- }
const preferences = this.store.query('preference', { organization_id: organization.id });
return hash({ organization, preferences });
},
diff --git a/app/routes/settings.js b/app/routes/settings.js
index bc781cfb1a..bcdb913827 100644
--- a/app/routes/settings.js
+++ b/app/routes/settings.js
@@ -67,8 +67,7 @@ export default TravisRoute.extend({
beforeModel() {
const repo = this.modelFor('repo');
- const hasPushPermission = this.permissions.hasPushPermission(repo);
- if (!hasPushPermission) {
+ if (!repo.permissions.settings_read) {
this.transitionTo('repo.index');
this.flashes.error('Your permissions are insufficient to access this repository\'s settings');
}
diff --git a/app/templates/components/billing/authorization.hbs b/app/templates/components/billing/authorization.hbs
index 802962c5fa..8614a179a9 100644
--- a/app/templates/components/billing/authorization.hbs
+++ b/app/templates/components/billing/authorization.hbs
@@ -47,6 +47,8 @@
{{else if (and this.isComplete (or this.subscription.isStripe this.subscription.isManual))}}
{{#if (or this.showPlansSelector this.showAddonsSelector)}}
+
+ {{#if this.hasSubscriptionPermissions }}
{{#if (not this.isV2Subscription)}}
{{format-currency this.selectedPlan.startingPrice floor="true"}}{{ if this.selectedPlan.isAnnual '/annualy' '/monthly'}}
- + {{#if this.planDetailsVisible}} @@ -174,7 +174,7 @@
{{stripeError.message}}
{{/if}} - +