feat!: rename right to permission (#1277)

This commit is contained in:
kolaente 2025-08-13 11:05:05 +02:00 committed by GitHub
parent 70eef88557
commit a81a3ee0e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
130 changed files with 872 additions and 752 deletions

View File

@ -139,7 +139,7 @@ linters:
text: 'G115: integer overflow conversion int -> uint64' text: 'G115: integer overflow conversion int -> uint64'
- linters: - linters:
- recvcheck - recvcheck
text: the methods of "Right" use pointer receiver and non-pointer receiver. text: the methods of "Permission" use pointer receiver and non-pointer receiver.
- linters: - linters:
- recvcheck - recvcheck
text: the methods of "SubscriptionEntityType" use pointer receiver and non-pointer receiver. text: the methods of "SubscriptionEntityType" use pointer receiver and non-pointer receiver.

View File

@ -73,7 +73,7 @@ The Go backend follows a layered architecture with clear separation of concerns:
**Key Patterns:** **Key Patterns:**
- **Generic CRUD**: Models implement `CRUDable` interface for standardized database operations - **Generic CRUD**: Models implement `CRUDable` interface for standardized database operations
- **Rights System**: Three-tier permissions (Read/Write/Admin) enforced across all operations - **Permissions System**: Three-tier permissions (Read/Write/Admin) enforced across all operations
- **Event-Driven**: Event system for notifications, webhooks, and cross-cutting concerns - **Event-Driven**: Event system for notifications, webhooks, and cross-cutting concerns
- **Modular Design**: Pluggable authentication, avatar providers, migration tools - **Modular Design**: Pluggable authentication, avatar providers, migration tools
@ -115,7 +115,7 @@ Modern Vue 3 composition API application with TypeScript:
### Adding New Features ### Adding New Features
**Backend Changes:** **Backend Changes:**
1. Create/modify models in `pkg/models/` with proper CRUD and Rights interfaces as required 1. Create/modify models in `pkg/models/` with proper CRUD and Permissions interfaces as required
2. Add database migration if needed: `mage dev:make-migration <StructName>` 2. Add database migration if needed: `mage dev:make-migration <StructName>`
3. Create/update services in `pkg/services/` for complex business logic 3. Create/update services in `pkg/services/` for complex business logic
4. Add API routes in `pkg/routes/api/v1/` following existing patterns 4. Add API routes in `pkg/routes/api/v1/` following existing patterns
@ -137,7 +137,7 @@ Modern Vue 3 composition API application with TypeScript:
### API Development ### API Development
- All API endpoints follow RESTful conventions under `/api/v1/` - All API endpoints follow RESTful conventions under `/api/v1/`
- Use generic web handlers in `pkg/web/handler/` for standard CRUD operations - Use generic web handlers in `pkg/web/handler/` for standard CRUD operations
- Implement proper rights checking using the Rights interface - Implement proper permissions checking using the Permissions interface
- Add Swagger annotations for automatic documentation generation - Add Swagger annotations for automatic documentation generation
### Testing ### Testing
@ -175,7 +175,7 @@ After adjusting the source string, you need to call the respective translation l
- Use `pkg/config/` for configuration management - Use `pkg/config/` for configuration management
**Code Style:** **Code Style:**
- Go: golangci-lint per `.golangci.yml`; use goimports; wrap errors with `fmt.Errorf("...: %w", err)`; enforce rights checks in models; never log secrets; do not edit generated `pkg/swagger/*` - Go: golangci-lint per `.golangci.yml`; use goimports; wrap errors with `fmt.Errorf("...: %w", err)`; enforce permissions checks in models; never log secrets; do not edit generated `pkg/swagger/*`
- Vue: ESLint + TS; single quotes, trailing commas, no semicolons, tab indent; script setup + lang ts; keep services/models in sync with backend - Vue: ESLint + TS; single quotes, trailing commas, no semicolons, tab indent; script setup + lang ts; keep services/models in sync with backend
- Follow existing patterns for consistency - Follow existing patterns for consistency
@ -184,16 +184,16 @@ After adjusting the source string, you need to call the respective translation l
- Vue: PascalCase for components, camelCase for composables - Vue: PascalCase for components, camelCase for composables
- API endpoints: kebab-case in URLs, camelCase in JSON - API endpoints: kebab-case in URLs, camelCase in JSON
**Rights and Permissions:** **Permissions and Permissions:**
- Always implement Rights interface for new models - Always implement Permissions interface for new models
- Use `CanRead`, `CanWrite`, `CanCreate`, `CanDelete` methods - Use `CanRead`, `CanWrite`, `CanCreate`, `CanDelete` methods
- Rights are enforced at the model level, not just routes - Permissions are enforced at the model level, not just routes
## Common Gotchas ## Common Gotchas
- Database migrations are irreversible in production - test thoroughly - Database migrations are irreversible in production - test thoroughly
- Frontend services must match backend model structure exactly - Frontend services must match backend model structure exactly
- Rights checking is mandatory for all CRUD operations - Permissions checking is mandatory for all CRUD operations
- Event listeners in `pkg/*/listeners.go` must be registered properly - Event listeners in `pkg/*/listeners.go` must be registered properly
- CORS settings in backend must allow frontend domain - CORS settings in backend must allow frontend domain
- API tokens have different scopes - check permissions carefully - API tokens have different scopes - check permissions carefully

View File

@ -59,7 +59,7 @@ describe('Project View List', () => {
UserProjectFactory.create(1, { UserProjectFactory.create(1, {
project_id: 2, project_id: 2,
user_id: 1, user_id: 1,
right: 0, permission: 0,
}) })
const projects = ProjectFactory.create(2, { const projects = ProjectFactory.create(2, {
owner_id: '{increment}', owner_id: '{increment}',

View File

@ -11,7 +11,7 @@ function prepareLinkShare() {
}) })
const linkShares = LinkShareFactory.create(1, { const linkShares = LinkShareFactory.create(1, {
project_id: projects[0].id, project_id: projects[0].id,
right: 0, permission: 0,
}) })
return { return {

View File

@ -11,7 +11,7 @@ export class LinkShareFactory extends Factory {
id: '{increment}', id: '{increment}',
hash: faker.lorem.word(32), hash: faker.lorem.word(32),
project_id: 1, project_id: 1,
right: 0, permission: 0,
sharing_type: 0, sharing_type: 0,
shared_by_id: 1, shared_by_id: 1,
created: now.toISOString(), created: now.toISOString(),

View File

@ -10,7 +10,7 @@ export class UserProjectFactory extends Factory {
id: '{increment}', id: '{increment}',
project_id: 1, project_id: 1,
user_id: 1, user_id: 1,
right: 0, permission: 0,
created: now.toISOString(), created: now.toISOString(),
updated: now.toISOString(), updated: now.toISOString(),
} }

View File

@ -116,7 +116,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { RIGHTS as Rights } from '@/constants/rights' import { PERMISSIONS as Permissions } from '@/constants/permissions'
import ProjectSettingsDropdown from '@/components/project/ProjectSettingsDropdown.vue' import ProjectSettingsDropdown from '@/components/project/ProjectSettingsDropdown.vue'
import Dropdown from '@/components/misc/Dropdown.vue' import Dropdown from '@/components/misc/Dropdown.vue'
@ -137,7 +137,7 @@ import { useAuthStore } from '@/stores/auth'
const baseStore = useBaseStore() const baseStore = useBaseStore()
const currentProject = computed(() => baseStore.currentProject) const currentProject = computed(() => baseStore.currentProject)
const background = computed(() => baseStore.background) const background = computed(() => baseStore.background)
const canWriteCurrentProject = computed(() => baseStore.currentProject?.maxRight > Rights.READ) const canWriteCurrentProject = computed(() => baseStore.currentProject?.maxPermission > Permissions.READ)
const menuActive = computed(() => baseStore.menuActive) const menuActive = computed(() => baseStore.menuActive)
const authStore = useAuthStore() const authStore = useAuthStore()

View File

@ -15,7 +15,7 @@
/> />
</BaseButton> </BaseButton>
<span <span
v-if="project.id > 0 && project.maxRight > RIGHTS.READ" v-if="project.id > 0 && project.maxPermission > PERMISSIONS.READ"
class="icon menu-item-icon handle drag-handle-standalone" class="icon menu-item-icon handle drag-handle-standalone"
@mousedown.stop @mousedown.stop
@click.stop.prevent @click.stop.prevent
@ -48,7 +48,7 @@
<span class="project-menu-title">{{ getProjectTitle(project) }}</span> <span class="project-menu-title">{{ getProjectTitle(project) }}</span>
</BaseButton> </BaseButton>
<BaseButton <BaseButton
v-if="project.id > 0 && project.maxRight > RIGHTS.READ" v-if="project.id > 0 && project.maxPermission > PERMISSIONS.READ"
class="favorite" class="favorite"
:class="{'is-favorite': project.isFavorite}" :class="{'is-favorite': project.isFavorite}"
@click="projectStore.toggleProjectFavorite(project)" @click="projectStore.toggleProjectFavorite(project)"
@ -57,7 +57,7 @@
<Icon :icon="project.isFavorite ? 'star' : ['far', 'star']" /> <Icon :icon="project.isFavorite ? 'star' : ['far', 'star']" />
</BaseButton> </BaseButton>
<ProjectSettingsDropdown <ProjectSettingsDropdown
v-if="project.maxRight > RIGHTS.READ" v-if="project.maxPermission > PERMISSIONS.READ"
class="menu-list-dropdown" class="menu-list-dropdown"
:project="project" :project="project"
> >
@ -97,7 +97,7 @@ import ProjectSettingsDropdown from '@/components/project/ProjectSettingsDropdow
import {getProjectTitle} from '@/helpers/getProjectTitle' import {getProjectTitle} from '@/helpers/getProjectTitle'
import ColorBubble from '@/components/misc/ColorBubble.vue' import ColorBubble from '@/components/misc/ColorBubble.vue'
import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue' import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue'
import {RIGHTS} from '@/constants/rights' import {PERMISSIONS} from '@/constants/permissions'
const props = defineProps<{ const props = defineProps<{
project: IProject, project: IProject,

View File

@ -110,7 +110,7 @@
{{ $t('menu.createProject') }} {{ $t('menu.createProject') }}
</DropdownItem> </DropdownItem>
<DropdownItem <DropdownItem
v-if="project.maxRight === RIGHTS.ADMIN" v-if="project.maxPermission === PERMISSIONS.ADMIN"
v-tooltip="isDefaultProject ? $t('menu.cantDeleteIsDefault') : ''" v-tooltip="isDefaultProject ? $t('menu.cantDeleteIsDefault') : ''"
:to="{ name: 'project.settings.delete', params: { projectId: project.id } }" :to="{ name: 'project.settings.delete', params: { projectId: project.id } }"
icon="trash-alt" icon="trash-alt"
@ -137,7 +137,7 @@ import {isSavedFilter} from '@/services/savedFilter'
import {useConfigStore} from '@/stores/config' import {useConfigStore} from '@/stores/config'
import {useProjectStore} from '@/stores/projects' import {useProjectStore} from '@/stores/projects'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
import {RIGHTS} from '@/constants/rights' import {PERMISSIONS} from '@/constants/permissions'
const props = defineProps<{ const props = defineProps<{
project: IProject project: IProject

View File

@ -77,7 +77,7 @@ const currentProject = computed<IProject>(() => {
id: 0, id: 0,
title: '', title: '',
isArchived: false, isArchived: false,
maxRight: null, maxPermission: null,
} : baseStore.currentProject } : baseStore.currentProject
}) })
useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value) : '') useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value) : '')

View File

@ -87,7 +87,7 @@ import TaskForm from '@/components/tasks/TaskForm.vue'
import GanttChart from '@/components/gantt/GanttChart.vue' import GanttChart from '@/components/gantt/GanttChart.vue'
import {useGanttFilters} from '../../../views/project/helpers/useGanttFilters' import {useGanttFilters} from '../../../views/project/helpers/useGanttFilters'
import {RIGHTS} from '@/constants/rights' import {PERMISSIONS} from '@/constants/permissions'
import type {DateISO} from '@/types/DateISO' import type {DateISO} from '@/types/DateISO'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
@ -103,7 +103,7 @@ const props = defineProps<{
const baseStore = useBaseStore() const baseStore = useBaseStore()
const canWrite = computed(() => baseStore.currentProject?.maxRight > RIGHTS.READ) const canWrite = computed(() => baseStore.currentProject?.maxPermission > PERMISSIONS.READ)
const {route, viewId} = toRefs(props) const {route, viewId} = toRefs(props)
const { const {

View File

@ -279,7 +279,7 @@ import {useI18n} from 'vue-i18n'
import draggable from 'zhyswan-vuedraggable' import draggable from 'zhyswan-vuedraggable'
import {klona} from 'klona/lite' import {klona} from 'klona/lite'
import {RIGHTS as Rights} from '@/constants/rights' import {PERMISSIONS as Permissions} from '@/constants/permissions'
import BucketModel from '@/models/bucket' import BucketModel from '@/models/bucket'
import type {IBucket} from '@/modelTypes/IBucket' import type {IBucket} from '@/modelTypes/IBucket'
@ -398,7 +398,7 @@ const bucketDraggableComponentData = computed(() => ({
})) }))
const project = computed(() => props.projectId ? projectStore.projects[props.projectId] : null) const project = computed(() => props.projectId ? projectStore.projects[props.projectId] : null)
const view = computed(() => project.value?.views.find(v => v.id === props.viewId) as IProjectView || null) const view = computed(() => project.value?.views.find(v => v.id === props.viewId) as IProjectView || null)
const canWrite = computed(() => baseStore.currentProject?.maxRight > Rights.READ && view.value.bucketConfigurationMode === 'manual') const canWrite = computed(() => baseStore.currentProject?.maxPermission > Permissions.READ && view.value.bucketConfigurationMode === 'manual')
const canCreateTasks = computed(() => canWrite.value && props.projectId > 0) const canCreateTasks = computed(() => canWrite.value && props.projectId > 0)
const buckets = computed(() => kanbanStore.buckets) const buckets = computed(() => kanbanStore.buckets)
const loading = computed(() => kanbanStore.isLoading) const loading = computed(() => kanbanStore.isLoading)

View File

@ -109,7 +109,7 @@ import Pagination from '@/components/misc/Pagination.vue'
import {ALPHABETICAL_SORT} from '@/components/project/partials/Filters.vue' import {ALPHABETICAL_SORT} from '@/components/project/partials/Filters.vue'
import {useTaskList} from '@/composables/useTaskList' import {useTaskList} from '@/composables/useTaskList'
import {RIGHTS as Rights} from '@/constants/rights' import {PERMISSIONS as Permissions} from '@/constants/permissions'
import {calculateItemPosition} from '@/helpers/calculateItemPosition' import {calculateItemPosition} from '@/helpers/calculateItemPosition'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import {isSavedFilter} from '@/services/savedFilter' import {isSavedFilter} from '@/services/savedFilter'
@ -182,7 +182,7 @@ const baseStore = useBaseStore()
const project = computed(() => baseStore.currentProject) const project = computed(() => baseStore.currentProject)
const canWrite = computed(() => { const canWrite = computed(() => {
return project.value.maxRight > Rights.READ && project.value.id > 0 return project.value.maxPermission > Permissions.READ && project.value.id > 0
}) })
const isPseudoProject = computed(() => (project.value && isSavedFilter(project.value)) || project.value?.id === -1) const isPseudoProject = computed(() => (project.value && isSavedFilter(project.value)) || project.value?.id === -1)

View File

@ -29,22 +29,22 @@
class="label" class="label"
for="linkShareRight" for="linkShareRight"
> >
{{ $t('project.share.right.title') }} {{ $t('project.share.permission.title') }}
</label> </label>
<div class="control"> <div class="control">
<div class="select"> <div class="select">
<select <select
id="linkShareRight" id="linkShareRight"
v-model="selectedRight" v-model="selectedPermission"
> >
<option :value="RIGHTS.READ"> <option :value="PERMISSIONS.READ">
{{ $t('project.share.right.read') }} {{ $t('project.share.permission.read') }}
</option> </option>
<option :value="RIGHTS.READ_WRITE"> <option :value="PERMISSIONS.READ_WRITE">
{{ $t('project.share.right.readWrite') }} {{ $t('project.share.permission.readWrite') }}
</option> </option>
<option :value="RIGHTS.ADMIN"> <option :value="PERMISSIONS.ADMIN">
{{ $t('project.share.right.admin') }} {{ $t('project.share.permission.admin') }}
</option> </option>
</select> </select>
</div> </div>
@ -129,23 +129,23 @@
</p> </p>
<p class="mbe-2"> <p class="mbe-2">
<template v-if="s.right === RIGHTS.ADMIN"> <template v-if="s.permission === PERMISSIONS.ADMIN">
<span class="icon is-small"> <span class="icon is-small">
<Icon icon="lock" /> <Icon icon="lock" />
</span>&nbsp; </span>&nbsp;
{{ $t('project.share.right.admin') }} {{ $t('project.share.permission.admin') }}
</template> </template>
<template v-else-if="s.right === RIGHTS.READ_WRITE"> <template v-else-if="s.permission === PERMISSIONS.READ_WRITE">
<span class="icon is-small"> <span class="icon is-small">
<Icon icon="pen" /> <Icon icon="pen" />
</span>&nbsp; </span>&nbsp;
{{ $t('project.share.right.readWrite') }} {{ $t('project.share.permission.readWrite') }}
</template> </template>
<template v-else> <template v-else>
<span class="icon is-small"> <span class="icon is-small">
<Icon icon="users" /> <Icon icon="users" />
</span>&nbsp; </span>&nbsp;
{{ $t('project.share.right.read') }} {{ $t('project.share.permission.read') }}
</template> </template>
</p> </p>
@ -221,7 +221,7 @@
import {ref, watch, computed, shallowReactive} from 'vue' import {ref, watch, computed, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import {RIGHTS} from '@/constants/rights' import {PERMISSIONS} from '@/constants/permissions'
import LinkShareModel from '@/models/linkShare' import LinkShareModel from '@/models/linkShare'
import type {ILinkShare} from '@/modelTypes/ILinkShare' import type {ILinkShare} from '@/modelTypes/ILinkShare'
@ -246,7 +246,7 @@ const {t} = useI18n({useScope: 'global'})
const linkShares = ref<ILinkShare[]>([]) const linkShares = ref<ILinkShare[]>([])
const linkShareService = shallowReactive(new LinkShareService()) const linkShareService = shallowReactive(new LinkShareService())
const selectedRight = ref(RIGHTS.READ) const selectedPermission = ref(PERMISSIONS.READ)
const name = ref('') const name = ref('')
const password = ref('') const password = ref('')
const showDeleteModal = ref(false) const showDeleteModal = ref(false)
@ -296,13 +296,13 @@ watch(() => ([linkShares.value, availableViews.value]), ([newLinkShares, newProj
async function add(projectId: IProject['id']) { async function add(projectId: IProject['id']) {
const newLinkShare = new LinkShareModel({ const newLinkShare = new LinkShareModel({
right: selectedRight.value, permission: selectedPermission.value,
projectId, projectId,
name: name.value, name: name.value,
password: password.value, password: password.value,
}) })
await linkShareService.create(newLinkShare) await linkShareService.create(newLinkShare)
selectedRight.value = RIGHTS.READ selectedPermission.value = PERMISSIONS.READ
name.value = '' name.value = ''
password.value = '' password.value = ''
showNewForm.value = false showNewForm.value = false

View File

@ -71,23 +71,23 @@
</td> </td>
</template> </template>
<td class="type"> <td class="type">
<template v-if="s.right === RIGHTS.ADMIN"> <template v-if="s.permission === PERMISSIONS.ADMIN">
<span class="icon is-small"> <span class="icon is-small">
<Icon icon="lock" /> <Icon icon="lock" />
</span> </span>
{{ $t('project.share.right.admin') }} {{ $t('project.share.permission.admin') }}
</template> </template>
<template v-else-if="s.right === RIGHTS.READ_WRITE"> <template v-else-if="s.permission === PERMISSIONS.READ_WRITE">
<span class="icon is-small"> <span class="icon is-small">
<Icon icon="pen" /> <Icon icon="pen" />
</span> </span>
{{ $t('project.share.right.readWrite') }} {{ $t('project.share.permission.readWrite') }}
</template> </template>
<template v-else> <template v-else>
<span class="icon is-small"> <span class="icon is-small">
<Icon icon="users" /> <Icon icon="users" />
</span> </span>
{{ $t('project.share.right.read') }} {{ $t('project.share.permission.read') }}
</template> </template>
</td> </td>
<td <td
@ -96,27 +96,27 @@
> >
<div class="select"> <div class="select">
<select <select
v-model="selectedRight[s.id]" v-model="selectedPermission[s.id]"
class="mie-2" class="mie-2"
@change="toggleType(s)" @change="toggleType(s)"
> >
<option <option
:selected="s.right === RIGHTS.READ" :selected="s.permission === PERMISSIONS.READ"
:value="RIGHTS.READ" :value="PERMISSIONS.READ"
> >
{{ $t('project.share.right.read') }} {{ $t('project.share.permission.read') }}
</option> </option>
<option <option
:selected="s.right === RIGHTS.READ_WRITE" :selected="s.permission === PERMISSIONS.READ_WRITE"
:value="RIGHTS.READ_WRITE" :value="PERMISSIONS.READ_WRITE"
> >
{{ $t('project.share.right.readWrite') }} {{ $t('project.share.permission.readWrite') }}
</option> </option>
<option <option
:selected="s.right === RIGHTS.ADMIN" :selected="s.permission === PERMISSIONS.ADMIN"
:value="RIGHTS.ADMIN" :value="PERMISSIONS.ADMIN"
> >
{{ $t('project.share.right.admin') }} {{ $t('project.share.permission.admin') }}
</option> </option>
</select> </select>
</div> </div>
@ -178,7 +178,7 @@ import TeamModel from '@/models/team'
import type {ITeam} from '@/modelTypes/ITeam' import type {ITeam} from '@/modelTypes/ITeam'
import {RIGHTS} from '@/constants/rights' import {PERMISSIONS} from '@/constants/permissions'
import Multiselect from '@/components/input/Multiselect.vue' import Multiselect from '@/components/input/Multiselect.vue'
import Nothing from '@/components/misc/Nothing.vue' import Nothing from '@/components/misc/Nothing.vue'
import {success} from '@/message' import {success} from '@/message'
@ -209,7 +209,7 @@ let searchService: UserService | TeamService
let sharable: Ref<IUser | ITeam> let sharable: Ref<IUser | ITeam>
const searchLabel = ref('') const searchLabel = ref('')
const selectedRight = ref({}) const selectedPermission = ref({})
// This holds either teams or users who this namepace or project is shared with // This holds either teams or users who this namepace or project is shared with
@ -275,8 +275,8 @@ load()
async function load() { async function load() {
sharables.value = await stuffService.getAll(stuffModel) sharables.value = await stuffService.getAll(stuffModel)
sharables.value.forEach(({id, right}) => sharables.value.forEach(({id, permission}) =>
selectedRight.value[id] = right, selectedPermission.value[id] = permission,
) )
} }
@ -309,9 +309,9 @@ async function add(admin) {
if (admin === null) { if (admin === null) {
admin = false admin = false
} }
stuffModel.right = RIGHTS.READ stuffModel.permission = PERMISSIONS.READ
if (admin) { if (admin) {
stuffModel.right = RIGHTS.ADMIN stuffModel.permission = PERMISSIONS.ADMIN
} }
if (props.shareType === 'user') { if (props.shareType === 'user') {
@ -327,13 +327,13 @@ async function add(admin) {
async function toggleType(sharable) { async function toggleType(sharable) {
if ( if (
selectedRight.value[sharable.id] !== RIGHTS.ADMIN && selectedPermission.value[sharable.id] !== PERMISSIONS.ADMIN &&
selectedRight.value[sharable.id] !== RIGHTS.READ && selectedPermission.value[sharable.id] !== PERMISSIONS.READ &&
selectedRight.value[sharable.id] !== RIGHTS.READ_WRITE selectedPermission.value[sharable.id] !== PERMISSIONS.READ_WRITE
) { ) {
selectedRight.value[sharable.id] = RIGHTS.READ selectedPermission.value[sharable.id] = PERMISSIONS.READ
} }
stuffModel.right = selectedRight.value[sharable.id] stuffModel.permission = selectedPermission.value[sharable.id]
if (props.shareType === 'user') { if (props.shareType === 'user') {
stuffModel.username = sharable.username stuffModel.username = sharable.username
@ -350,7 +350,7 @@ async function toggleType(sharable) {
(sharables.value[i].id === stuffModel.teamId && (sharables.value[i].id === stuffModel.teamId &&
props.shareType === 'team') props.shareType === 'team')
) { ) {
sharables.value[i].right = r.right sharables.value[i].permission = r.permission
} }
} }
success({message: t('project.share.userTeam.updatedSuccess', {type: shareTypeName.value})}) success({message: t('project.share.userTeam.updatedSuccess', {type: shareTypeName.value})})

View File

@ -0,0 +1,7 @@
export const PERMISSIONS = {
'READ': 0,
'READ_WRITE': 1,
'ADMIN': 2,
} as const
export type Permission = typeof PERMISSIONS[keyof typeof PERMISSIONS]

View File

@ -1,7 +0,0 @@
export const RIGHTS = {
'READ': 0,
'READ_WRITE': 1,
'ADMIN': 2,
} as const
export type Right = typeof RIGHTS[keyof typeof RIGHTS]

View File

@ -1,5 +1,5 @@
import type {Right} from '@/constants/rights' import type {Permission} from '@/constants/permissions'
export interface IAbstract { export interface IAbstract {
maxRight: Right | null // FIXME: should this be readonly? maxPermission: Permission | null // FIXME: should this be readonly?
} }

View File

@ -1,11 +1,11 @@
import type {IAbstract} from './IAbstract' import type {IAbstract} from './IAbstract'
import type {IUser} from './IUser' import type {IUser} from './IUser'
import type { Right } from '@/constants/rights' import type { Permission } from '@/constants/permissions'
export interface ILinkShare extends IAbstract { export interface ILinkShare extends IAbstract {
id: number id: number
hash: string hash: string
right: Right permission: Permission
sharedBy: IUser sharedBy: IUser
sharingType: number // FIXME: use correct numbers sharingType: number // FIXME: use correct numbers
projectId: number projectId: number

View File

@ -1,14 +1,14 @@
import type {IAbstract} from './IAbstract' import type {IAbstract} from './IAbstract'
import type {IUser} from './IUser' import type {IUser} from './IUser'
import type {ITeamMember} from './ITeamMember' import type {ITeamMember} from './ITeamMember'
import type {Right} from '@/constants/rights' import type {Permission} from '@/constants/permissions'
export interface ITeam extends IAbstract { export interface ITeam extends IAbstract {
id: number id: number
name: string name: string
description: string description: string
members: ITeamMember[] members: ITeamMember[]
right: Right permission: Permission
externalId: string externalId: string
isPublic: boolean isPublic: boolean

View File

@ -1,10 +1,10 @@
import type {IAbstract} from './IAbstract' import type {IAbstract} from './IAbstract'
import type {ITeam} from './ITeam' import type {ITeam} from './ITeam'
import type {Right} from '@/constants/rights' import type {Permission} from '@/constants/permissions'
export interface ITeamShareBase extends IAbstract { export interface ITeamShareBase extends IAbstract {
teamId: ITeam['id'] teamId: ITeam['id']
right: Right permission: Permission
created: Date created: Date
updated: Date updated: Date

View File

@ -1,10 +1,10 @@
import type {IAbstract} from './IAbstract' import type {IAbstract} from './IAbstract'
import type {IUser} from './IUser' import type {IUser} from './IUser'
import type {Right} from '@/constants/rights' import type {Permission} from '@/constants/permissions'
export interface IUserShareBase extends IAbstract { export interface IUserShareBase extends IAbstract {
username: IUser['username'] username: IUser['username']
right: Right permission: Permission
created: Date created: Date
updated: Date updated: Date

View File

@ -1,15 +1,15 @@
import {objectToCamelCase} from '@/helpers/case' import {objectToCamelCase} from '@/helpers/case'
import {omitBy, isNil} from '@/helpers/utils' import {omitBy, isNil} from '@/helpers/utils'
import type {Right} from '@/constants/rights' import type {Permission} from '@/constants/permissions'
import type {IAbstract} from '@/modelTypes/IAbstract' import type {IAbstract} from '@/modelTypes/IAbstract'
export default abstract class AbstractModel<Model extends IAbstract = IAbstract> implements IAbstract { export default abstract class AbstractModel<Model extends IAbstract = IAbstract> implements IAbstract {
/** /**
* The max right the user has on this object, as returned by the x-max-right header from the api. * The max permission the user has on this object, as returned by the x-max-permission header from the api.
*/ */
maxRight: Right | null = null maxPermission: Permission | null = null
/** /**
* Takes an object and merges its data with the default data of this model. * Takes an object and merges its data with the default data of this model.

View File

@ -1,14 +1,14 @@
import AbstractModel from './abstractModel' import AbstractModel from './abstractModel'
import UserModel from './user' import UserModel from './user'
import {RIGHTS, type Right} from '@/constants/rights' import {PERMISSIONS, type Permission} from '@/constants/permissions'
import type {ILinkShare} from '@/modelTypes/ILinkShare' import type {ILinkShare} from '@/modelTypes/ILinkShare'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class LinkShareModel extends AbstractModel<ILinkShare> implements ILinkShare { export default class LinkShareModel extends AbstractModel<ILinkShare> implements ILinkShare {
id = 0 id = 0
hash = '' hash = ''
right: Right = RIGHTS.READ permission: Permission = PERMISSIONS.READ
sharedBy: IUser = UserModel sharedBy: IUser = UserModel
sharingType = 0 // FIXME: use correct numbers sharingType = 0 // FIXME: use correct numbers
projectId = 0 projectId = 0

View File

@ -2,7 +2,7 @@ import AbstractModel from './abstractModel'
import UserModel from './user' import UserModel from './user'
import TeamMemberModel from './teamMember' import TeamMemberModel from './teamMember'
import {RIGHTS, type Right} from '@/constants/rights' import {PERMISSIONS, type Permission} from '@/constants/permissions'
import type {ITeam} from '@/modelTypes/ITeam' import type {ITeam} from '@/modelTypes/ITeam'
import type {ITeamMember} from '@/modelTypes/ITeamMember' import type {ITeamMember} from '@/modelTypes/ITeamMember'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
@ -12,7 +12,7 @@ export default class TeamModel extends AbstractModel<ITeam> implements ITeam {
name = '' name = ''
description = '' description = ''
members: ITeamMember[] = [] members: ITeamMember[] = []
right: Right = RIGHTS.READ permission: Permission = PERMISSIONS.READ
externalId = '' externalId = ''
isPublic: boolean = false isPublic: boolean = false

View File

@ -1,6 +1,6 @@
import AbstractModel from './abstractModel' import AbstractModel from './abstractModel'
import {RIGHTS, type Right} from '@/constants/rights' import {PERMISSIONS, type Permission} from '@/constants/permissions'
import type {ITeamShareBase} from '@/modelTypes/ITeamShareBase' import type {ITeamShareBase} from '@/modelTypes/ITeamShareBase'
import type {ITeam} from '@/modelTypes/ITeam' import type {ITeam} from '@/modelTypes/ITeam'
@ -10,7 +10,7 @@ import type {ITeam} from '@/modelTypes/ITeam'
*/ */
export default class TeamShareBaseModel extends AbstractModel<ITeamShareBase> implements ITeamShareBase { export default class TeamShareBaseModel extends AbstractModel<ITeamShareBase> implements ITeamShareBase {
teamId: ITeam['id'] = 0 teamId: ITeam['id'] = 0
right: Right = RIGHTS.READ permission: Permission = PERMISSIONS.READ
created: Date = null created: Date = null
updated: Date = null updated: Date = null

View File

@ -3,7 +3,7 @@ import UserShareBaseModel from './userShareBase'
import type {IUserProject} from '@/modelTypes/IUserProject' import type {IUserProject} from '@/modelTypes/IUserProject'
import type {IProject} from '@/modelTypes/IProject' import type {IProject} from '@/modelTypes/IProject'
// This class extends the user share model with a 'rights' parameter which is used in sharing // This class extends the user share model with a 'permissions' parameter which is used in sharing
export default class UserProjectModel extends UserShareBaseModel implements IUserProject { export default class UserProjectModel extends UserShareBaseModel implements IUserProject {
projectId: IProject['id'] = 0 projectId: IProject['id'] = 0

View File

@ -1,12 +1,12 @@
import AbstractModel from './abstractModel' import AbstractModel from './abstractModel'
import {RIGHTS, type Right} from '@/constants/rights' import {PERMISSIONS, type Permission} from '@/constants/permissions'
import type {IUserShareBase} from '@/modelTypes/IUserShareBase' import type {IUserShareBase} from '@/modelTypes/IUserShareBase'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class UserShareBaseModel extends AbstractModel<IUserShareBase> implements IUserShareBase { export default class UserShareBaseModel extends AbstractModel<IUserShareBase> implements IUserShareBase {
username: IUser['username'] = '' username: IUser['username'] = ''
right: Right = RIGHTS.READ permission: Permission = PERMISSIONS.READ
created: Date = null created: Date = null
updated: Date = null updated: Date = null

View File

@ -4,7 +4,7 @@ import type {Method} from 'axios'
import {objectToSnakeCase} from '@/helpers/case' import {objectToSnakeCase} from '@/helpers/case'
import AbstractModel from '@/models/abstractModel' import AbstractModel from '@/models/abstractModel'
import type {IAbstract} from '@/modelTypes/IAbstract' import type {IAbstract} from '@/modelTypes/IAbstract'
import type {Right} from '@/constants/rights' import type {Permission} from '@/constants/permissions'
interface Paths { interface Paths {
create : string create : string
@ -310,7 +310,7 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
try { try {
const response = await this.http.get(finalUrl, {params: prepareParams(params)}) const response = await this.http.get(finalUrl, {params: prepareParams(params)})
const result = this.modelGetFactory(response.data) const result = this.modelGetFactory(response.data)
result.maxRight = Number(response.headers['x-max-right']) as Right result.maxPermission = Number(response.headers['x-max-permission']) as Permission
return result return result
} finally { } finally {
cancel() cancel()
@ -386,8 +386,8 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
try { try {
const response = await this.http.put(finalUrl, model) const response = await this.http.put(finalUrl, model)
const result = this.modelCreateFactory(response.data) const result = this.modelCreateFactory(response.data)
if (typeof model.maxRight !== 'undefined') { if (typeof model.maxPermission !== 'undefined') {
result.maxRight = model.maxRight result.maxPermission = model.maxPermission
} }
return result return result
} finally { } finally {
@ -405,8 +405,8 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
try { try {
const response = await this.http.post(url, model) const response = await this.http.post(url, model)
const result = this.modelUpdateFactory(response.data) const result = this.modelUpdateFactory(response.data)
if (typeof model.maxRight !== 'undefined') { if (typeof model.maxPermission !== 'undefined') {
result.maxRight = model.maxRight result.maxPermission = model.maxPermission
} }
return result return result
} finally { } finally {

View File

@ -12,7 +12,7 @@ import {useMenuActive} from '@/composables/useMenuActive'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
import type {IProject} from '@/modelTypes/IProject' import type {IProject} from '@/modelTypes/IProject'
import type {Right} from '@/constants/rights' import type {Permission} from '@/constants/permissions'
import type {IProjectView} from '@/modelTypes/IProjectView' import type {IProjectView} from '@/modelTypes/IProjectView'
export const useBaseStore = defineStore('base', () => { export const useBaseStore = defineStore('base', () => {
@ -40,19 +40,19 @@ export const useBaseStore = defineStore('base', () => {
const updateAvailable = ref(false) const updateAvailable = ref(false)
function setCurrentProject(newCurrentProject: IProject | null, currentViewId?: IProjectView['id']) { function setCurrentProject(newCurrentProject: IProject | null, currentViewId?: IProjectView['id']) {
// Server updates don't return the right. Therefore, the right is reset after updating the project which is // Server updates don't return the permission. Therefore, the permission is reset after updating the project which is
// confusing because all the buttons will disappear in that case. To prevent this, we're keeping the right // confusing because all the buttons will disappear in that case. To prevent this, we're keeping the permission
// when updating the project in global state. // when updating the project in global state.
let maxRight: Right | null = newCurrentProject?.maxRight || null let maxPermission: Permission | null = newCurrentProject?.maxPermission || null
if ( if (
typeof currentProject.value?.maxRight !== 'undefined' && typeof currentProject.value?.maxPermission !== 'undefined' &&
newCurrentProject !== null && newCurrentProject !== null &&
( (
typeof newCurrentProject.maxRight === 'undefined' || typeof newCurrentProject.maxPermission === 'undefined' ||
newCurrentProject.maxRight === null newCurrentProject.maxPermission === null
) )
) { ) {
maxRight = currentProject.value.maxRight maxPermission = currentProject.value.maxPermission
} }
if (newCurrentProject === null) { if (newCurrentProject === null) {
currentProject.value = null currentProject.value = null
@ -60,7 +60,7 @@ export const useBaseStore = defineStore('base', () => {
} }
currentProject.value = { currentProject.value = {
...newCurrentProject, ...newCurrentProject,
maxRight, maxPermission,
} }
setCurrentProjectViewId(currentViewId) setCurrentProjectViewId(currentViewId)
} }

View File

@ -17,7 +17,7 @@ import {success} from '@/message'
import {useBaseStore} from '@/stores/base' import {useBaseStore} from '@/stores/base'
import {getSavedFilterIdFromProjectId} from '@/services/savedFilter' import {getSavedFilterIdFromProjectId} from '@/services/savedFilter'
import type {IProjectView} from '@/modelTypes/IProjectView' import type {IProjectView} from '@/modelTypes/IProjectView'
import {RIGHTS} from '@/constants/rights.ts' import {PERMISSIONS} from '@/constants/permissions.ts'
const {add, remove, search, update} = createNewIndexer('projects', ['title', 'description']) const {add, remove, search, update} = createNewIndexer('projects', ['title', 'description'])
@ -213,7 +213,7 @@ export const useProjectStore = defineStore('project', () => {
let page = 1 let page = 1
try { try {
do { do {
const newProjects = await projectService.getAll({}, {is_archived: true, expand: 'rights'}, page) as IProject[] const newProjects = await projectService.getAll({}, {is_archived: true, expand: 'permissions'}, page) as IProject[]
loadedProjects.push(...newProjects) loadedProjects.push(...newProjects)
page++ page++
} while (page <= projectService.totalPages) } while (page <= projectService.totalPages)
@ -320,7 +320,7 @@ export function useProject(projectId: MaybeRefOrGetter<IProject['id']>) {
const duplicate = await projectDuplicateService.create(projectDuplicate) const duplicate = await projectDuplicateService.create(projectDuplicate)
if (duplicate.duplicatedProject) { if (duplicate.duplicatedProject) {
duplicate.duplicatedProject.maxRight = RIGHTS.ADMIN duplicate.duplicatedProject.maxPermission = PERMISSIONS.ADMIN
} }
projectStore.setProject(duplicate.duplicatedProject) projectStore.setProject(duplicate.duplicatedProject)

View File

@ -3,7 +3,7 @@
:title="$t('project.edit.header')" :title="$t('project.edit.header')"
primary-icon="" primary-icon=""
:primary-label="$t('misc.save')" :primary-label="$t('misc.save')"
:tertiary="project.maxRight === RIGHTS.ADMIN ? $t('misc.delete') : undefined" :tertiary="project.maxPermission === PERMISSIONS.ADMIN ? $t('misc.delete') : undefined"
@primary="save" @primary="save"
@tertiary="$router.push({ name: 'project.settings.delete', params: { id: projectId } })" @tertiary="$router.push({ name: 'project.settings.delete', params: { id: projectId } })"
> >
@ -100,7 +100,7 @@ import {useProjectStore} from '@/stores/projects'
import {useProject} from '@/stores/projects' import {useProject} from '@/stores/projects'
import {useTitle} from '@/composables/useTitle' import {useTitle} from '@/composables/useTitle'
import {RIGHTS} from '@/constants/rights' import {PERMISSIONS} from '@/constants/permissions'
const props = defineProps<{ const props = defineProps<{
projectId: IProject['id'], projectId: IProject['id'],

View File

@ -34,7 +34,7 @@ import {useTitle} from '@vueuse/core'
import ProjectService from '@/services/project' import ProjectService from '@/services/project'
import ProjectModel from '@/models/project' import ProjectModel from '@/models/project'
import type {IProject} from '@/modelTypes/IProject' import type {IProject} from '@/modelTypes/IProject'
import {RIGHTS} from '@/constants/rights' import {PERMISSIONS} from '@/constants/permissions'
import CreateEdit from '@/components/misc/CreateEdit.vue' import CreateEdit from '@/components/misc/CreateEdit.vue'
import LinkSharing from '@/components/sharing/LinkSharing.vue' import LinkSharing from '@/components/sharing/LinkSharing.vue'
@ -57,7 +57,7 @@ useTitle(title)
const configStore = useConfigStore() const configStore = useConfigStore()
const linkSharingEnabled = computed(() => configStore.linkSharingEnabled) const linkSharingEnabled = computed(() => configStore.linkSharingEnabled)
const userIsAdmin = computed(() => project?.value?.maxRight === RIGHTS.ADMIN) const userIsAdmin = computed(() => project?.value?.maxPermission === PERMISSIONS.ADMIN)
async function loadProject(projectId: number) { async function loadProject(projectId: number) {
const projectService = new ProjectService() const projectService = new ProjectService()

View File

@ -10,7 +10,7 @@ import XButton from '@/components/input/Button.vue'
import {error, success} from '@/message' import {error, success} from '@/message'
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import ProjectService from '@/services/project' import ProjectService from '@/services/project'
import {RIGHTS} from '@/constants/rights' import {PERMISSIONS} from '@/constants/permissions'
import ProjectModel from '@/models/project' import ProjectModel from '@/models/project'
import Message from '@/components/misc/Message.vue' import Message from '@/components/misc/Message.vue'
import draggable from 'zhyswan-vuedraggable' import draggable from 'zhyswan-vuedraggable'
@ -49,7 +49,7 @@ watch(
async () => { async () => {
const projectService = new ProjectService() const projectService = new ProjectService()
const project = await projectService.get(new ProjectModel({id: props.projectId})) const project = await projectService.get(new ProjectModel({id: props.projectId}))
isAdmin.value = project.maxRight === RIGHTS.ADMIN isAdmin.value = project.maxPermission === PERMISSIONS.ADMIN
}, },
{immediate: true}, {immediate: true},
) )

View File

@ -603,7 +603,7 @@ import type {ITask} from '@/modelTypes/ITask'
import type {IProject} from '@/modelTypes/IProject' import type {IProject} from '@/modelTypes/IProject'
import {PRIORITIES, type Priority} from '@/constants/priorities' import {PRIORITIES, type Priority} from '@/constants/priorities'
import {RIGHTS} from '@/constants/rights' import {PERMISSIONS} from '@/constants/permissions'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
@ -703,8 +703,8 @@ const visible = ref(false)
const project = computed(() => projectStore.projects[task.value.projectId]) const project = computed(() => projectStore.projects[task.value.projectId])
const canWrite = computed(() => ( const canWrite = computed(() => (
task.value.maxRight !== null && task.value.maxPermission !== null &&
task.value.maxRight > RIGHTS.READ task.value.maxPermission > PERMISSIONS.READ
)) ))
const color = computed(() => { const color = computed(() => {

View File

@ -272,7 +272,7 @@ import TeamService from '@/services/team'
import TeamMemberService from '@/services/teamMember' import TeamMemberService from '@/services/teamMember'
import UserService from '@/services/user' import UserService from '@/services/user'
import {RIGHTS as Rights} from '@/constants/rights' import {PERMISSIONS as Permissions} from '@/constants/permissions'
import {useTitle} from '@/composables/useTitle' import {useTitle} from '@/composables/useTitle'
import {success} from '@/message' import {success} from '@/message'
@ -292,8 +292,8 @@ const {t} = useI18n({useScope: 'global'})
const userIsAdmin = computed(() => { const userIsAdmin = computed(() => {
return ( return (
team.value && team.value &&
team.value.maxRight && team.value.maxPermission &&
team.value.maxRight > Rights.READ team.value.maxPermission > Permissions.READ
) )
}) })
const userInfo = computed(() => authStore.info) const userInfo = computed(() => authStore.info)

View File

@ -1,7 +1,7 @@
- id: 1 - id: 1
hash: test hash: test
project_id: 1 project_id: 1
right: 0 permission: 0
sharing_type: 1 sharing_type: 1
shared_by_id: 1 shared_by_id: 1
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
@ -9,7 +9,7 @@
- id: 2 - id: 2
hash: test2 hash: test2
project_id: 2 project_id: 2
right: 1 permission: 1
sharing_type: 1 sharing_type: 1
shared_by_id: 1 shared_by_id: 1
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
@ -17,7 +17,7 @@
- id: 3 - id: 3
hash: test3 hash: test3
project_id: 3 project_id: 3
right: 2 permission: 2
sharing_type: 1 sharing_type: 1
shared_by_id: 1 shared_by_id: 1
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
@ -26,7 +26,7 @@
hash: testWithPassword hash: testWithPassword
name: testWithPassword name: testWithPassword
project_id: 1 project_id: 1
right: 0 permission: 0
password: '$2a$04$X4aRMEt0ytgPwMIgv36cI..7X9.nhY/.tYwxpqSi0ykRHx2CwQ0S6' # 12345678 password: '$2a$04$X4aRMEt0ytgPwMIgv36cI..7X9.nhY/.tYwxpqSi0ykRHx2CwQ0S6' # 12345678
sharing_type: 2 sharing_type: 2
shared_by_id: 1 shared_by_id: 1

View File

@ -1,96 +1,96 @@
- id: 1 - id: 1
team_id: 1 team_id: 1
project_id: 3 project_id: 3
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
# This team has read only access on project 6 # This team has read only access on project 6
- id: 2 - id: 2
team_id: 2 team_id: 2
project_id: 6 project_id: 6
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
# This team has write access on project 7 # This team has write access on project 7
- id: 3 - id: 3
team_id: 3 team_id: 3
project_id: 7 project_id: 7
right: 1 permission: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
# This team has admin access on project 8 # This team has admin access on project 8
- id: 4 - id: 4
team_id: 4 team_id: 4
project_id: 8 project_id: 8
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
# Readonly acces on project 19 # Readonly acces on project 19
- id: 5 - id: 5
team_id: 8 team_id: 8
project_id: 19 project_id: 19
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
# Write acces on project 19 # Write acces on project 19
- id: 6 - id: 6
team_id: 9 team_id: 9
project_id: 19 project_id: 19
right: 1 permission: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
# Admin acces on project 19 # Admin acces on project 19
- id: 7 - id: 7
team_id: 10 team_id: 10
project_id: 19 project_id: 19
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 8 - id: 8
team_id: 1 team_id: 1
project_id: 21 project_id: 21
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 9 - id: 9
team_id: 1 team_id: 1
project_id: 28 project_id: 28
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 10 - id: 10
team_id: 11 team_id: 11
project_id: 29 project_id: 29
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 11 - id: 11
team_id: 12 team_id: 12
project_id: 29 project_id: 29
right: 1 permission: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 12 - id: 12
team_id: 13 team_id: 13
project_id: 29 project_id: 29
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 13 - id: 13
team_id: 1 team_id: 1
project_id: 32 project_id: 32
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 14 - id: 14
team_id: 1 team_id: 1
project_id: 33 project_id: 33
right: 1 permission: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 15 - id: 15
team_id: 1 team_id: 1
project_id: 34 project_id: 34
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12

View File

@ -1,114 +1,114 @@
- id: 1 - id: 1
user_id: 1 user_id: 1
project_id: 3 project_id: 3
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 2 - id: 2
user_id: 2 user_id: 2
project_id: 3 project_id: 3
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 3 - id: 3
user_id: 1 user_id: 1
project_id: 9 project_id: 9
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 4 - id: 4
user_id: 1 user_id: 1
project_id: 10 project_id: 10
right: 1 permission: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 5 - id: 5
user_id: 1 user_id: 1
project_id: 11 project_id: 11
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 6 - id: 6
user_id: 4 user_id: 4
project_id: 19 project_id: 19
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 7 - id: 7
user_id: 5 user_id: 5
project_id: 19 project_id: 19
right: 1 permission: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 8 - id: 8
user_id: 6 user_id: 6
project_id: 19 project_id: 19
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 9 - id: 9
user_id: 1 user_id: 1
project_id: 27 project_id: 27
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 10 - id: 10
user_id: 11 user_id: 11
project_id: 29 project_id: 29
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 11 - id: 11
user_id: 12 user_id: 12
project_id: 29 project_id: 29
right: 1 permission: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 12 - id: 12
user_id: 13 user_id: 13
project_id: 29 project_id: 29
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 13 - id: 13
user_id: 1 user_id: 1
project_id: 30 project_id: 30
right: 1 permission: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 14 - id: 14
user_id: 1 user_id: 1
project_id: 31 project_id: 31
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 15 - id: 15
user_id: 1 user_id: 1
project_id: 28 project_id: 28
right: 1 permission: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 16 - id: 16
user_id: 1 user_id: 1
project_id: 29 project_id: 29
right: 2 permission: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 17 - id: 17
user_id: 15 user_id: 15
project_id: 26 project_id: 26
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 18 - id: 18
user_id: 15 user_id: 15
project_id: 36 project_id: 36
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 19 - id: 19
user_id: 15 user_id: 15
project_id: 38 project_id: 38
right: 0 permission: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12

View File

@ -45,7 +45,7 @@ type Opts struct {
// ContentType represents mail content types // ContentType represents mail content types
type ContentType int type ContentType int
// Enumerate all the team rights // Enumerate all the team permissions
const ( const (
ContentTypePlain ContentType = iota ContentTypePlain ContentType = iota
ContentTypeHTML ContentTypeHTML

View File

@ -26,7 +26,7 @@ type linkSharing20190818210133 struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"` ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"` Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"`
ListID int64 `xorm:"int(11) not null" json:"list_id"` ListID int64 `xorm:"int(11) not null" json:"list_id"`
Right models.Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` Right models.Permission `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
SharingType models.SharingType `xorm:"int(11) INDEX not null default 0" json:"sharing_type" valid:"length(0|2)" maximum:"2" default:"0"` SharingType models.SharingType `xorm:"int(11) INDEX not null default 0" json:"sharing_type" valid:"length(0|2)" maximum:"2" default:"0"`
SharedByID int64 `xorm:"int(11) INDEX not null"` SharedByID int64 `xorm:"int(11) INDEX not null"`
Created int64 `xorm:"created not null" json:"created"` Created int64 `xorm:"created not null" json:"created"`

View File

@ -0,0 +1,44 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package migration
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20250813093602",
Description: "rename 'right' column to 'permission' in link_shares, users_projects, and team_projects tables",
Migrate: func(tx *xorm.Engine) error {
tables := []string{"link_shares", "users_projects", "team_projects"}
for _, table := range tables {
err := renameColumn(tx, table, "right", "permission")
if err != nil {
return err
}
}
return nil
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}

View File

@ -186,6 +186,82 @@ func renameTable(x *xorm.Engine, oldName, newName string) error {
return nil return nil
} }
// Checks if a column exists in a table
func columnExists(x *xorm.Engine, tableName, columnName string) (bool, error) {
switch config.DatabaseType.GetString() {
case "sqlite":
results, err := x.Query("PRAGMA table_info(" + tableName + ")")
if err != nil {
return false, err
}
for _, row := range results {
if name, ok := row["name"]; ok && string(name) == columnName {
return true, nil
}
}
return false, nil
case "mysql":
results, err := x.Query("SHOW COLUMNS FROM `" + tableName + "` LIKE '" + columnName + "'")
if err != nil {
return false, err
}
return len(results) > 0, nil
case "postgres":
results, err := x.Query("SELECT column_name FROM information_schema.columns WHERE table_name = '" + tableName + "' AND column_name = '" + columnName + "'")
if err != nil {
return false, err
}
return len(results) > 0, nil
default:
log.Fatal("Unknown db.")
return false, nil
}
}
func renameColumn(x *xorm.Engine, tableName, oldColumn, newColumn string) error {
// Check if old column exists
exists, err := columnExists(x, tableName, oldColumn)
if err != nil {
return err
}
if !exists {
log.Debugf("Column %s in table %s does not exist, skipping rename", oldColumn, tableName)
return nil
}
// Check if new column already exists
newExists, err := columnExists(x, tableName, newColumn)
if err != nil {
return err
}
if newExists {
log.Debugf("Column %s in table %s already exists, skipping rename", newColumn, tableName)
return nil
}
switch config.DatabaseType.GetString() {
case "sqlite":
_, err := x.Exec("ALTER TABLE \"" + tableName + "\" RENAME COLUMN \"" + oldColumn + "\" TO \"" + newColumn + "\"")
if err != nil {
return err
}
case "mysql":
_, err := x.Exec("ALTER TABLE `" + tableName + "` CHANGE `" + oldColumn + "` `" + newColumn + "` BIGINT NOT NULL DEFAULT 0")
if err != nil {
return err
}
case "postgres":
_, err := x.Exec("ALTER TABLE \"" + tableName + "\" RENAME COLUMN \"" + oldColumn + "\" TO \"" + newColumn + "\"")
if err != nil {
return err
}
default:
log.Fatal("Unknown db.")
}
return nil
}
func initSchema(tx *xorm.Engine) error { func initSchema(tx *xorm.Engine) error {
schemeBeans := []interface{}{} schemeBeans := []interface{}{}
schemeBeans = append(schemeBeans, models.GetTables()...) schemeBeans = append(schemeBeans, models.GetTables()...)

View File

@ -244,9 +244,9 @@ func CanDoAPIRoute(c echo.Context, token *APIToken) (can bool) {
routeGroupName = "other" routeGroupName = "other"
} }
group, hasGroup := token.Permissions[routeGroupName] group, hasGroup := token.APIPermissions[routeGroupName]
if !hasGroup { if !hasGroup {
group, hasGroup = token.Permissions[routeParts[0]] group, hasGroup = token.APIPermissions[routeParts[0]]
if !hasGroup { if !hasGroup {
return false return false
} }
@ -276,7 +276,7 @@ func CanDoAPIRoute(c echo.Context, token *APIToken) (can bool) {
} }
} }
log.Debugf("[auth] Token %d tried to use route %s which requires permission %s but has only %v", token.ID, path, route, token.Permissions) log.Debugf("[auth] Token %d tried to use route %s which requires permission %s but has only %v", token.ID, path, route, token.APIPermissions)
return false return false
} }

View File

@ -22,13 +22,12 @@ import (
"encoding/hex" "encoding/hex"
"time" "time"
"xorm.io/builder"
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/utils" "code.vikunja.io/api/pkg/utils"
"code.vikunja.io/api/pkg/web" "code.vikunja.io/api/pkg/web"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
) )
@ -46,7 +45,7 @@ type APIToken struct {
TokenHash string `xorm:"not null unique" json:"-"` TokenHash string `xorm:"not null unique" json:"-"`
TokenLastEight string `xorm:"not null index varchar(8)" json:"-"` TokenLastEight string `xorm:"not null index varchar(8)" json:"-"`
// The permissions this token has. Possible values are available via the /routes endpoint and consist of the keys of the list from that endpoint. For example, if the token should be able to read all tasks as well as update existing tasks, you should add `{"tasks":["read_all","update"]}`. // The permissions this token has. Possible values are available via the /routes endpoint and consist of the keys of the list from that endpoint. For example, if the token should be able to read all tasks as well as update existing tasks, you should add `{"tasks":["read_all","update"]}`.
Permissions APIPermissions `xorm:"json not null" json:"permissions" valid:"required"` APIPermissions APIPermissions `xorm:"json not null permissions" json:"permissions" valid:"required"`
// The date when this key expires. // The date when this key expires.
ExpiresAt time.Time `xorm:"not null" json:"expires_at" valid:"required"` ExpiresAt time.Time `xorm:"not null" json:"expires_at" valid:"required"`
@ -55,8 +54,8 @@ type APIToken struct {
OwnerID int64 `xorm:"bigint not null" json:"-"` OwnerID int64 `xorm:"bigint not null" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
} }
const APITokenPrefix = `tk_` const APITokenPrefix = `tk_`
@ -102,7 +101,7 @@ func (t *APIToken) Create(s *xorm.Session, a web.Auth) (err error) {
t.OwnerID = a.GetID() t.OwnerID = a.GetID()
if err := PermissionsAreValid(t.Permissions); err != nil { if err := PermissionsAreValid(t.APIPermissions); err != nil {
return err return err
} }

View File

@ -564,31 +564,31 @@ func (err ErrBulkTasksNeedAtLeastOne) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksNeedAtLeastOne, Message: "Need at least one tasks to do bulk editing."} return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksNeedAtLeastOne, Message: "Need at least one tasks to do bulk editing."}
} }
// ErrNoRightToSeeTask represents an error where a user does not have the right to see a task // ErrNoPermissionToSeeTask represents an error where a user does not have the permission to see a task
type ErrNoRightToSeeTask struct { type ErrNoPermissionToSeeTask struct {
TaskID int64 TaskID int64
UserID int64 UserID int64
} }
// IsErrNoRightToSeeTask checks if an error is ErrNoRightToSeeTask. // IsErrNoPermissionToSeeTask checks if an error is ErrNoPermissionToSeeTask.
func IsErrNoRightToSeeTask(err error) bool { func IsErrNoPermissionToSeeTask(err error) bool {
_, ok := err.(ErrNoRightToSeeTask) _, ok := err.(ErrNoPermissionToSeeTask)
return ok return ok
} }
func (err ErrNoRightToSeeTask) Error() string { func (err ErrNoPermissionToSeeTask) Error() string {
return fmt.Sprintf("User does not have the right to see the task [TaskID: %v, ID: %v]", err.TaskID, err.UserID) return fmt.Sprintf("User does not have the permission to see the task [TaskID: %v, ID: %v]", err.TaskID, err.UserID)
} }
// ErrCodeNoRightToSeeTask holds the unique world-error code of this error // ErrCodeNoRightToSeeTask holds the unique world-error code of this error
const ErrCodeNoRightToSeeTask = 4005 const ErrCodeNoRightToSeeTask = 4005
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrNoRightToSeeTask) HTTPError() web.HTTPError { func (err ErrNoPermissionToSeeTask) HTTPError() web.HTTPError {
return web.HTTPError{ return web.HTTPError{
HTTPCode: http.StatusForbidden, HTTPCode: http.StatusForbidden,
Code: ErrCodeNoRightToSeeTask, Code: ErrCodeNoRightToSeeTask,
Message: "You don't have the right to see this task.", Message: "You don't have the permission to see this task.",
} }
} }
@ -1497,7 +1497,7 @@ func (err ErrLabelDoesNotExist) HTTPError() web.HTTPError {
} }
} }
// ErrUserHasNoAccessToLabel represents an error where a user does not have the right to see a label // ErrUserHasNoAccessToLabel represents an error where a user does not have the permission to see a label
type ErrUserHasNoAccessToLabel struct { type ErrUserHasNoAccessToLabel struct {
LabelID int64 LabelID int64
UserID int64 UserID int64
@ -1526,33 +1526,33 @@ func (err ErrUserHasNoAccessToLabel) HTTPError() web.HTTPError {
} }
// ======== // ========
// Rights // Permissions
// ======== // ========
// ErrInvalidRight represents an error where a right is invalid // ErrInvalidPermission represents an error where a permission is invalid
type ErrInvalidRight struct { type ErrInvalidPermission struct {
Right Right Permission Permission
} }
// IsErrInvalidRight checks if an error is ErrInvalidRight. // IsErrInvalidPermission checks if an error is ErrInvalidPermission.
func IsErrInvalidRight(err error) bool { func IsErrInvalidPermission(err error) bool {
_, ok := err.(ErrInvalidRight) _, ok := err.(ErrInvalidPermission)
return ok return ok
} }
func (err ErrInvalidRight) Error() string { func (err ErrInvalidPermission) Error() string {
return fmt.Sprintf("Right invalid [Right: %d]", err.Right) return fmt.Sprintf("Permission invalid [Permission: %d]", err.Permission)
} }
// ErrCodeInvalidRight holds the unique world-error code of this error // ErrCodeInvalidRight holds the unique world-error code of this error
const ErrCodeInvalidRight = 9001 const ErrCodeInvalidRight = 9001
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrInvalidRight) HTTPError() web.HTTPError { func (err ErrInvalidPermission) HTTPError() web.HTTPError {
return web.HTTPError{ return web.HTTPError{
HTTPCode: http.StatusBadRequest, HTTPCode: http.StatusBadRequest,
Code: ErrCodeInvalidRight, Code: ErrCodeInvalidRight,
Message: "The right is invalid.", Message: "The permission is invalid.",
} }
} }

View File

@ -61,8 +61,8 @@ type Bucket struct {
// Including the task collection type so we can use task filters on kanban // Including the task collection type so we can use task filters on kanban
TaskCollection `xorm:"-" json:"-"` TaskCollection `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
} }
// TableName returns the table name for this bucket. // TableName returns the table name for this bucket.

View File

@ -42,7 +42,7 @@ func (b *Bucket) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
return b.canDoBucket(s, a) return b.canDoBucket(s, a)
} }
// canDoBucket checks if the bucket exists and if the user has the right to act on it // canDoBucket checks if the bucket exists and if the user has the permission to act on it
func (b *Bucket) canDoBucket(s *xorm.Session, a web.Auth) (bool, error) { func (b *Bucket) canDoBucket(s *xorm.Session, a web.Auth) (bool, error) {
bb, err := getBucketByID(s, b.ID) bb, err := getBucketByID(s, b.ID)
if err != nil { if err != nil {

View File

@ -41,8 +41,8 @@ type TaskBucket struct {
ProjectID int64 `xorm:"-" json:"-" param:"project"` ProjectID int64 `xorm:"-" json:"-" param:"project"`
Task *Task `xorm:"-" json:"task"` Task *Task `xorm:"-" json:"task"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
} }
func (b *TaskBucket) TableName() string { func (b *TaskBucket) TableName() string {

View File

@ -120,9 +120,9 @@ func TestBucket_ReadAll(t *testing.T) {
defer s.Close() defer s.Close()
linkShare := &LinkSharing{ linkShare := &LinkSharing{
ID: 1, ID: 1,
ProjectID: 1, ProjectID: 1,
Right: RightRead, Permission: PermissionRead,
} }
b := &TaskCollection{ b := &TaskCollection{
ProjectID: 1, ProjectID: 1,

View File

@ -46,8 +46,8 @@ type Label struct {
// A timestamp when this label was last updated. You cannot change this value. // A timestamp when this label was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"` Updated time.Time `xorm:"updated not null" json:"updated"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName makes a pretty table name // TableName makes a pretty table name

View File

@ -61,7 +61,7 @@ func (l *Label) isLabelOwner(s *xorm.Session, a web.Auth) (bool, error) {
} }
// Helper method to check if a user can see a specific label // Helper method to check if a user can see a specific label
func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRight int, err error) { func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxPermission int, err error) {
linkShare, isLinkShare := a.(*LinkSharing) linkShare, isLinkShare := a.(*LinkSharing)
@ -93,10 +93,10 @@ func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRigh
return return
} }
// Since the right depends on the task the label is associated with, we need to check that too. // Since the permission depends on the task the label is associated with, we need to check that too.
if ll.TaskID > 0 { if ll.TaskID > 0 {
t := &Task{ID: ll.TaskID} t := &Task{ID: ll.TaskID}
_, maxRight, err = t.CanRead(s, a) _, maxPermission, err = t.CanRead(s, a)
if err != nil { if err != nil {
return return
} }

View File

@ -40,8 +40,8 @@ type LabelTask struct {
// A timestamp when this task was created. You cannot change this value. // A timestamp when this task was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName makes a pretty table name // TableName makes a pretty table name
@ -127,14 +127,14 @@ func (lt *LabelTask) Create(s *xorm.Session, auth web.Auth) (err error) {
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{task}/labels [get] // @Router /tasks/{task}/labels [get]
func (lt *LabelTask) ReadAll(s *xorm.Session, a web.Auth, search string, page int, _ int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { func (lt *LabelTask) ReadAll(s *xorm.Session, a web.Auth, search string, page int, _ int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
// Check if the user has the right to see the task // Check if the user has the permission to see the task
task := Task{ID: lt.TaskID} task := Task{ID: lt.TaskID}
canRead, _, err := task.CanRead(s, a) canRead, _, err := task.CanRead(s, a)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
if !canRead { if !canRead {
return nil, 0, 0, ErrNoRightToSeeTask{lt.TaskID, a.GetID()} return nil, 0, 0, ErrNoPermissionToSeeTask{lt.TaskID, a.GetID()}
} }
return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{ return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
@ -377,7 +377,7 @@ func (t *Task) UpdateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Lab
return err return err
} }
// Check if the user has the rights to see the label he is about to add // Check if the user has the permissions to see the label he is about to add
hasAccessToLabel, _, err := label.hasAccessToLabel(s, creator) hasAccessToLabel, _, err := label.hasAccessToLabel(s, creator)
if err != nil { if err != nil {
return err return err
@ -413,8 +413,8 @@ type LabelTaskBulk struct {
Labels []*Label `json:"labels"` Labels []*Label `json:"labels"`
TaskID int64 `json:"-" param:"projecttask"` TaskID int64 `json:"-" param:"projecttask"`
web.CRUDable `json:"-"` web.CRUDable `json:"-"`
web.Rights `json:"-"` web.Permissions `json:"-"`
} }
// Create updates a bunch of labels on a task at once // Create updates a bunch of labels on a task at once

View File

@ -51,12 +51,12 @@ func TestLabelTask_ReadAll(t *testing.T) {
} }
type fields struct { type fields struct {
ID int64 ID int64
TaskID int64 TaskID int64
LabelID int64 LabelID int64
Created time.Time Created time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
type args struct { type args struct {
search string search string
@ -87,7 +87,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
}, },
}, },
{ {
name: "no right to see the task", name: "no permission to see the task",
fields: fields{ fields: fields{
TaskID: 14, TaskID: 14,
}, },
@ -95,7 +95,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
a: &user.User{ID: 1}, a: &user.User{ID: 1},
}, },
wantErr: true, wantErr: true,
errType: IsErrNoRightToSeeTask, errType: IsErrNoPermissionToSeeTask,
}, },
{ {
name: "nonexistant task", name: "nonexistant task",
@ -131,12 +131,12 @@ func TestLabelTask_ReadAll(t *testing.T) {
s := db.NewSession() s := db.NewSession()
l := &LabelTask{ l := &LabelTask{
ID: tt.fields.ID, ID: tt.fields.ID,
TaskID: tt.fields.TaskID, TaskID: tt.fields.TaskID,
LabelID: tt.fields.LabelID, LabelID: tt.fields.LabelID,
Created: tt.fields.Created, Created: tt.fields.Created,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
gotLabels, _, _, err := l.ReadAll(s, tt.args.a, tt.args.search, tt.args.page, 0) gotLabels, _, _, err := l.ReadAll(s, tt.args.a, tt.args.search, tt.args.page, 0)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
@ -157,12 +157,12 @@ func TestLabelTask_ReadAll(t *testing.T) {
func TestLabelTask_Create(t *testing.T) { func TestLabelTask_Create(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
TaskID int64 TaskID int64
LabelID int64 LabelID int64
Created time.Time Created time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
type args struct { type args struct {
a web.Auth a web.Auth
@ -229,12 +229,12 @@ func TestLabelTask_Create(t *testing.T) {
s := db.NewSession() s := db.NewSession()
l := &LabelTask{ l := &LabelTask{
ID: tt.fields.ID, ID: tt.fields.ID,
TaskID: tt.fields.TaskID, TaskID: tt.fields.TaskID,
LabelID: tt.fields.LabelID, LabelID: tt.fields.LabelID,
Created: tt.fields.Created, Created: tt.fields.Created,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
allowed, err := l.CanCreate(s, tt.args.a) allowed, err := l.CanCreate(s, tt.args.a)
if !allowed && !tt.wantForbidden { if !allowed && !tt.wantForbidden {
@ -261,12 +261,12 @@ func TestLabelTask_Create(t *testing.T) {
func TestLabelTask_Delete(t *testing.T) { func TestLabelTask_Delete(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
TaskID int64 TaskID int64
LabelID int64 LabelID int64
Created time.Time Created time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
tests := []struct { tests := []struct {
name string name string
@ -329,12 +329,12 @@ func TestLabelTask_Delete(t *testing.T) {
defer s.Close() defer s.Close()
l := &LabelTask{ l := &LabelTask{
ID: tt.fields.ID, ID: tt.fields.ID,
TaskID: tt.fields.TaskID, TaskID: tt.fields.TaskID,
LabelID: tt.fields.LabelID, LabelID: tt.fields.LabelID,
Created: tt.fields.Created, Created: tt.fields.Created,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
allowed, _ := l.CanDelete(s, tt.auth) allowed, _ := l.CanDelete(s, tt.auth)
if !allowed && !tt.wantForbidden { if !allowed && !tt.wantForbidden {

View File

@ -40,7 +40,7 @@ func TestLabel_ReadAll(t *testing.T) {
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
type args struct { type args struct {
search string search string
@ -146,7 +146,7 @@ func TestLabel_ReadAll(t *testing.T) {
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
@ -177,7 +177,7 @@ func TestLabel_ReadOne(t *testing.T) {
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
user1 := &user.User{ user1 := &user.User{
ID: 1, ID: 1,
@ -226,7 +226,7 @@ func TestLabel_ReadOne(t *testing.T) {
auth: &user.User{ID: 1}, auth: &user.User{ID: 1},
}, },
{ {
name: "no rights", name: "no permissions",
fields: fields{ fields: fields{
ID: 3, ID: 3,
}, },
@ -272,7 +272,7 @@ func TestLabel_ReadOne(t *testing.T) {
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
s := db.NewSession() s := db.NewSession()
@ -308,7 +308,7 @@ func TestLabel_Create(t *testing.T) {
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
type args struct { type args struct {
a web.Auth a web.Auth
@ -344,7 +344,7 @@ func TestLabel_Create(t *testing.T) {
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
s := db.NewSession() s := db.NewSession()
allowed, _ := l.CanCreate(s, tt.args.a) allowed, _ := l.CanCreate(s, tt.args.a)
@ -378,7 +378,7 @@ func TestLabel_Update(t *testing.T) {
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
tests := []struct { tests := []struct {
name string name string
@ -406,7 +406,7 @@ func TestLabel_Update(t *testing.T) {
wantErr: true, wantErr: true,
}, },
{ {
name: "no rights", name: "no permissions",
fields: fields{ fields: fields{
ID: 3, ID: 3,
Title: "new and better", Title: "new and better",
@ -415,7 +415,7 @@ func TestLabel_Update(t *testing.T) {
wantForbidden: true, wantForbidden: true,
}, },
{ {
name: "no rights other creator but access", name: "no permissions other creator but access",
fields: fields{ fields: fields{
ID: 4, ID: 4,
Title: "new and better", Title: "new and better",
@ -436,7 +436,7 @@ func TestLabel_Update(t *testing.T) {
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
s := db.NewSession() s := db.NewSession()
allowed, _ := l.CanUpdate(s, tt.auth) allowed, _ := l.CanUpdate(s, tt.auth)
@ -468,7 +468,7 @@ func TestLabel_Delete(t *testing.T) {
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
tests := []struct { tests := []struct {
name string name string
@ -494,7 +494,7 @@ func TestLabel_Delete(t *testing.T) {
wantForbidden: true, // When the label does not exist, it is forbidden. We should fix this, but for everything. wantForbidden: true, // When the label does not exist, it is forbidden. We should fix this, but for everything.
}, },
{ {
name: "no rights", name: "no permissions",
fields: fields{ fields: fields{
ID: 3, ID: 3,
}, },
@ -502,7 +502,7 @@ func TestLabel_Delete(t *testing.T) {
wantForbidden: true, wantForbidden: true,
}, },
{ {
name: "no rights but visible", name: "no permissions but visible",
fields: fields{ fields: fields{
ID: 4, ID: 4,
}, },
@ -522,7 +522,7 @@ func TestLabel_Delete(t *testing.T) {
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
s := db.NewSession() s := db.NewSession()
allowed, _ := l.CanDelete(s, tt.auth) allowed, _ := l.CanDelete(s, tt.auth)

View File

@ -52,8 +52,8 @@ type LinkSharing struct {
Name string `xorm:"text null" json:"name"` Name string `xorm:"text null" json:"name"`
// The ID of the shared project // The ID of the shared project
ProjectID int64 `xorm:"bigint not null" json:"-" param:"project"` ProjectID int64 `xorm:"bigint not null" json:"-" param:"project"`
// The right this project is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. // The permission this project is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.
Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` Permission Permission `xorm:"bigint INDEX not null default 0" json:"permission" valid:"length(0|2)" maximum:"2" default:"0"`
// The kind of this link. 0 = undefined, 1 = without password, 2 = with password. // The kind of this link. 0 = undefined, 1 = without password, 2 = with password.
SharingType SharingType `xorm:"bigint INDEX not null default 0" json:"sharing_type" valid:"length(0|2)" maximum:"2" default:"0"` SharingType SharingType `xorm:"bigint INDEX not null default 0" json:"sharing_type" valid:"length(0|2)" maximum:"2" default:"0"`
@ -70,8 +70,8 @@ type LinkSharing struct {
// A timestamp when this share was last updated. You cannot change this value. // A timestamp when this share was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"` Updated time.Time `xorm:"updated not null" json:"updated"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName holds the table name // TableName holds the table name
@ -95,7 +95,7 @@ func GetLinkShareFromClaims(claims jwt.MapClaims) (share *LinkSharing, err error
share.ID = int64(claims["id"].(float64)) share.ID = int64(claims["id"].(float64))
share.Hash = claims["hash"].(string) share.Hash = claims["hash"].(string)
share.ProjectID = int64(projectID) share.ProjectID = int64(projectID)
share.Right = Right(claims["right"].(float64)) share.Permission = Permission(claims["permission"].(float64))
share.SharedByID = int64(claims["sharedByID"].(float64)) share.SharedByID = int64(claims["sharedByID"].(float64))
return return
} }
@ -138,7 +138,7 @@ func (share *LinkSharing) toUser() *user.User {
// @Router /projects/{project}/shares [put] // @Router /projects/{project}/shares [put]
func (share *LinkSharing) Create(s *xorm.Session, a web.Auth) (err error) { func (share *LinkSharing) Create(s *xorm.Session, a web.Auth) (err error) {
err = share.Right.isValid() err = share.Permission.isValid()
if err != nil { if err != nil {
return return
} }

View File

@ -21,7 +21,7 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// CanRead implements the read right check for a link share // CanRead implements the read permission check for a link share
func (share *LinkSharing) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) { func (share *LinkSharing) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
// Don't allow creating link shares if the user itself authenticated with a link share // Don't allow creating link shares if the user itself authenticated with a link share
if _, is := a.(*LinkSharing); is { if _, is := a.(*LinkSharing); is {
@ -35,17 +35,17 @@ func (share *LinkSharing) CanRead(s *xorm.Session, a web.Auth) (bool, int, error
return l.CanRead(s, a) return l.CanRead(s, a)
} }
// CanDelete implements the delete right check for a link share // CanDelete implements the delete permission check for a link share
func (share *LinkSharing) CanDelete(s *xorm.Session, a web.Auth) (bool, error) { func (share *LinkSharing) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
return share.canDoLinkShare(s, a) return share.canDoLinkShare(s, a)
} }
// CanUpdate implements the update right check for a link share // CanUpdate implements the update permission check for a link share
func (share *LinkSharing) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { func (share *LinkSharing) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
return share.canDoLinkShare(s, a) return share.canDoLinkShare(s, a)
} }
// CanCreate implements the create right check for a link share // CanCreate implements the create permission check for a link share
func (share *LinkSharing) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { func (share *LinkSharing) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
return share.canDoLinkShare(s, a) return share.canDoLinkShare(s, a)
} }
@ -61,8 +61,8 @@ func (share *LinkSharing) canDoLinkShare(s *xorm.Session, a web.Auth) (bool, err
return false, err return false, err
} }
// Check if the user is admin when the link right is admin // Check if the user is admin when the link permission is admin
if share.Right == RightAdmin { if share.Permission == PermissionAdmin {
return l.IsAdmin(s, a) return l.IsAdmin(s, a)
} }

View File

@ -36,8 +36,8 @@ func TestLinkSharing_Create(t *testing.T) {
defer s.Close() defer s.Close()
share := &LinkSharing{ share := &LinkSharing{
ProjectID: 1, ProjectID: 1,
Right: RightRead, Permission: PermissionRead,
} }
err := share.Create(s, doer) err := share.Create(s, doer)
@ -49,19 +49,19 @@ func TestLinkSharing_Create(t *testing.T) {
"id": share.ID, "id": share.ID,
}, false) }, false)
}) })
t.Run("invalid right", func(t *testing.T) { t.Run("invalid permission", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
share := &LinkSharing{ share := &LinkSharing{
ProjectID: 1, ProjectID: 1,
Right: Right(123), Permission: Permission(123),
} }
err := share.Create(s, doer) err := share.Create(s, doer)
require.Error(t, err) require.Error(t, err)
assert.True(t, IsErrInvalidRight(err)) assert.True(t, IsErrInvalidPermission(err))
}) })
t.Run("password should be hashed", func(t *testing.T) { t.Run("password should be hashed", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
@ -69,9 +69,9 @@ func TestLinkSharing_Create(t *testing.T) {
defer s.Close() defer s.Close()
share := &LinkSharing{ share := &LinkSharing{
ProjectID: 1, ProjectID: 1,
Right: RightRead, Permission: PermissionRead,
Password: "somePassword", Password: "somePassword",
} }
err := share.Create(s, doer) err := share.Create(s, doer)

View File

@ -30,8 +30,8 @@ type DatabaseNotifications struct {
// True is read, false is unread. // True is read, false is unread.
Read bool `xorm:"-" json:"read"` Read bool `xorm:"-" json:"read"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// ReadAll returns all database notifications for a user // ReadAll returns all database notifications for a user

View File

@ -22,38 +22,38 @@ import (
"strconv" "strconv"
) )
// Right defines the rights users/teams can have for projects // Permission defines the permissions users/teams can have for projects
type Right int type Permission int
// define unknown right // define unknown permission
const ( const (
RightUnknown = -1 PermissionUnknown = -1
// Can read projects in a // Can read projects in a
RightRead Right = iota - 1 PermissionRead Permission = iota - 1
// Can write in a like projects and tasks. Cannot create new projects. // Can write in a like projects and tasks. Cannot create new projects.
RightWrite PermissionWrite
// Can manage a project, can do everything // Can manage a project, can do everything
RightAdmin PermissionAdmin
) )
func (r Right) isValid() error { func (r Permission) isValid() error {
if r != RightAdmin && r != RightRead && r != RightWrite { if r != PermissionAdmin && r != PermissionRead && r != PermissionWrite {
return ErrInvalidRight{r} return ErrInvalidPermission{r}
} }
return nil return nil
} }
// MarshalJSON marshals the enum as a quoted json string // MarshalJSON marshals the enum as a quoted json string
func (r Right) MarshalJSON() ([]byte, error) { func (r Permission) MarshalJSON() ([]byte, error) {
if r == RightUnknown { if r == PermissionUnknown {
return []byte(`null`), nil return []byte(`null`), nil
} }
return []byte(strconv.Itoa(int(r))), nil return []byte(strconv.Itoa(int(r))), nil
} }
// UnmarshalJSON unmarshals a quoted json string to the enum value // UnmarshalJSON unmarshals a quoted json string to the enum value
func (r *Right) UnmarshalJSON(data []byte) error { func (r *Permission) UnmarshalJSON(data []byte) error {
var s int var s int
if err := json.Unmarshal(data, &s); err != nil { if err := json.Unmarshal(data, &s); err != nil {
return err return err
@ -61,15 +61,15 @@ func (r *Right) UnmarshalJSON(data []byte) error {
switch s { switch s {
case -1: case -1:
*r = RightUnknown *r = PermissionUnknown
case 0: case 0:
*r = RightRead *r = PermissionRead
case 1: case 1:
*r = RightWrite *r = PermissionWrite
case 2: case 2:
*r = RightAdmin *r = PermissionAdmin
default: default:
return fmt.Errorf("invalid Right %q", s) return fmt.Errorf("invalid Permission %q", s)
} }
return nil return nil
} }

View File

@ -77,21 +77,21 @@ type Project struct {
Views []*ProjectView `xorm:"-" json:"views"` Views []*ProjectView `xorm:"-" json:"views"`
Expand ProjectExpandable `xorm:"-" json:"-" query:"expand"` Expand ProjectExpandable `xorm:"-" json:"-" query:"expand"`
MaxRight Right `xorm:"-" json:"max_right"` MaxPermission Permission `xorm:"-" json:"max_permission"`
// A timestamp when this project was created. You cannot change this value. // A timestamp when this project was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
// A timestamp when this project was last updated. You cannot change this value. // A timestamp when this project was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"` Updated time.Time `xorm:"updated not null" json:"updated"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
type ProjectExpandable string type ProjectExpandable string
const ProjectExpandableRights = `rights` const ProjectExpandableRights = `permissions`
type ProjectWithTasksAndBuckets struct { type ProjectWithTasksAndBuckets struct {
Project Project
@ -168,7 +168,7 @@ var FavoritesPseudoProject = Project{
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search projects by title." // @Param s query string false "Search projects by title."
// @Param is_archived query bool false "If true, also returns all archived projects." // @Param is_archived query bool false "If true, also returns all archived projects."
// @Param expand query string false "If set to `rights`, Vikunja will return the max right the current user has on this project. You can currently only set this to `rights`." // @Param expand query string false "If set to `permissions`, Vikunja will return the max permission the current user has on this project. You can currently only set this to `permissions`."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.Project "The projects" // @Success 200 {array} models.Project "The projects"
// @Failure 403 {object} web.HTTPError "The user does not have access to the project" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
@ -200,13 +200,13 @@ func (p *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
if err != nil { if err != nil {
return return
} }
err = addMaxRightToProjects(s, prs, doer) err = addMaxPermissionToProjects(s, prs, doer)
if err != nil { if err != nil {
return return
} }
} else { } else {
for _, pr := range prs { for _, pr := range prs {
pr.MaxRight = RightUnknown pr.MaxPermission = PermissionUnknown
} }
} }
@ -765,25 +765,25 @@ func addProjectDetails(s *xorm.Session, projects []*Project, a web.Auth) (err er
return return
} }
func addMaxRightToProjects(s *xorm.Session, projects []*Project, u *user.User) (err error) { func addMaxPermissionToProjects(s *xorm.Session, projects []*Project, u *user.User) (err error) {
projectIDs := make([]int64, 0, len(projects)) projectIDs := make([]int64, 0, len(projects))
for _, project := range projects { for _, project := range projects {
if GetSavedFilterIDFromProjectID(project.ID) > 0 { if GetSavedFilterIDFromProjectID(project.ID) > 0 {
project.MaxRight = RightAdmin project.MaxPermission = PermissionAdmin
continue continue
} }
projectIDs = append(projectIDs, project.ID) projectIDs = append(projectIDs, project.ID)
} }
rights, err := checkRightsForProjects(s, u, projectIDs) permissions, err := checkPermissionsForProjects(s, u, projectIDs)
if err != nil { if err != nil {
return err return err
} }
for _, project := range projects { for _, project := range projects {
right, has := rights[project.ID] permission, has := permissions[project.ID]
if has { if has {
project.MaxRight = right.MaxRight project.MaxPermission = permission.MaxPermission
} }
} }

View File

@ -35,11 +35,11 @@ type ProjectDuplicate struct {
// The copied project // The copied project
Project *Project `json:"duplicated_project,omitempty"` Project *Project `json:"duplicated_project,omitempty"`
web.Rights `json:"-"` web.Permissions `json:"-"`
web.CRUDable `json:"-"` web.CRUDable `json:"-"`
} }
// CanCreate checks if a user has the right to duplicate a project // CanCreate checks if a user has the permission to duplicate a project
func (pd *ProjectDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bool, err error) { func (pd *ProjectDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bool, err error) {
// Project Exists + user has read access to project // Project Exists + user has read access to project
pd.Project = &Project{ID: pd.ProjectID} pd.Project = &Project{ID: pd.ProjectID}
@ -59,7 +59,7 @@ func (pd *ProjectDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bo
// Create duplicates a project // Create duplicates a project
// @Summary Duplicate an existing project // @Summary Duplicate an existing project
// @Description Copies the project, tasks, files, kanban data, assignees, comments, attachments, labels, relations, backgrounds, user/team rights and link shares from one project to a new one. The user needs read access in the project and write access in the parent of the new project. // @Description Copies the project, tasks, files, kanban data, assignees, comments, attachments, labels, relations, backgrounds, user/team permissions and link shares from one project to a new one. The user needs read access in the project and write access in the parent of the new project.
// @tags project // @tags project
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -114,8 +114,8 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
return return
} }
// Rights / Shares // Permissions / Shares
// To keep it simple(r) we will only copy rights which are directly used with the project, not the parent // To keep it simple(r) we will only copy permissions which are directly used with the project, not the parent
users := []*ProjectUser{} users := []*ProjectUser{}
err = s.Where("project_id = ?", pd.ProjectID).Find(&users) err = s.Where("project_id = ?", pd.ProjectID).Find(&users)
if err != nil { if err != nil {

View File

@ -22,6 +22,7 @@ import (
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils" "code.vikunja.io/api/pkg/utils"
"code.vikunja.io/api/pkg/web" "code.vikunja.io/api/pkg/web"
"xorm.io/xorm" "xorm.io/xorm"
) )
@ -33,7 +34,7 @@ func (p *Project) CanWrite(s *xorm.Session, a web.Auth) (bool, error) {
return false, nil return false, nil
} }
// Get the project and check the right // Get the project and check the permission
originalProject, err := GetProjectSimpleByID(s, p.ID) originalProject, err := GetProjectSimpleByID(s, p.ID)
if err != nil { if err != nil {
return false, err return false, err
@ -49,7 +50,7 @@ func (p *Project) CanWrite(s *xorm.Session, a web.Auth) (bool, error) {
shareAuth, ok := a.(*LinkSharing) shareAuth, ok := a.(*LinkSharing)
if ok { if ok {
return originalProject.ID == shareAuth.ProjectID && return originalProject.ID == shareAuth.ProjectID &&
(shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), errIsArchived (shareAuth.Permission == PermissionWrite || shareAuth.Permission == PermissionAdmin), errIsArchived
} }
u := &user.User{ID: a.GetID()} u := &user.User{ID: a.GetID()}
@ -63,7 +64,7 @@ func (p *Project) CanWrite(s *xorm.Session, a web.Auth) (bool, error) {
return canWrite, errIsArchived return canWrite, errIsArchived
} }
canWrite, _, err = originalProject.checkRight(s, u, RightWrite, RightAdmin) canWrite, _, err = originalProject.checkPermission(s, u, PermissionWrite, PermissionAdmin)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -82,7 +83,7 @@ func (p *Project) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
*p = FavoritesPseudoProject *p = FavoritesPseudoProject
p.Owner = owner p.Owner = owner
return true, int(RightRead), nil return true, int(PermissionRead), nil
} }
// Saved Filter Projects need a special case // Saved Filter Projects need a special case
@ -104,10 +105,10 @@ func (p *Project) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
shareAuth, ok := a.(*LinkSharing) shareAuth, ok := a.(*LinkSharing)
if ok { if ok {
return p.ID == shareAuth.ProjectID && return p.ID == shareAuth.ProjectID &&
(shareAuth.Right == RightRead || shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), int(shareAuth.Right), nil (shareAuth.Permission == PermissionRead || shareAuth.Permission == PermissionWrite || shareAuth.Permission == PermissionAdmin), int(shareAuth.Permission), nil
} }
return p.checkRight(s, &user.User{ID: a.GetID()}, RightRead, RightWrite, RightAdmin) return p.checkPermission(s, &user.User{ID: a.GetID()}, PermissionRead, PermissionWrite, PermissionAdmin)
} }
// CanUpdate checks if the user can update a project // CanUpdate checks if the user can update a project
@ -175,7 +176,7 @@ func (p *Project) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
return true, nil return true, nil
} }
// IsAdmin returns whether the user has admin rights on the project or not // IsAdmin returns whether the user has admin permissions on the project or not
func (p *Project) IsAdmin(s *xorm.Session, a web.Auth) (bool, error) { func (p *Project) IsAdmin(s *xorm.Session, a web.Auth) (bool, error) {
// The favorite project can't be edited // The favorite project can't be edited
if p.ID == FavoritesPseudoProject.ID { if p.ID == FavoritesPseudoProject.ID {
@ -190,7 +191,7 @@ func (p *Project) IsAdmin(s *xorm.Session, a web.Auth) (bool, error) {
// Check if we're dealing with a share auth // Check if we're dealing with a share auth
shareAuth, ok := a.(*LinkSharing) shareAuth, ok := a.(*LinkSharing)
if ok { if ok {
return originalProject.ID == shareAuth.ProjectID && shareAuth.Right == RightAdmin, nil return originalProject.ID == shareAuth.ProjectID && shareAuth.Permission == PermissionAdmin, nil
} }
u := &user.User{ID: a.GetID()} u := &user.User{ID: a.GetID()}
@ -201,7 +202,7 @@ func (p *Project) IsAdmin(s *xorm.Session, a web.Auth) (bool, error) {
if originalProject.isOwner(u) { if originalProject.isOwner(u) {
return true, nil return true, nil
} }
is, _, err := originalProject.checkRight(s, u, RightAdmin) is, _, err := originalProject.checkPermission(s, u, PermissionAdmin)
return is, err return is, err
} }
@ -210,33 +211,33 @@ func (p *Project) isOwner(u *user.User) bool {
return p.OwnerID == u.ID return p.OwnerID == u.ID
} }
// Checks n different rights for any given user // Checks n different permissions for any given user
func (p *Project) checkRight(s *xorm.Session, u *user.User, rights ...Right) (bool, int, error) { func (p *Project) checkPermission(s *xorm.Session, u *user.User, permissions ...Permission) (bool, int, error) {
projectRights, err := checkRightsForProjects(s, u, []int64{p.ID}) projectPermissions, err := checkPermissionsForProjects(s, u, []int64{p.ID})
if err != nil { if err != nil {
return false, 0, err return false, 0, err
} }
right, has := projectRights[p.ID] permission, has := projectPermissions[p.ID]
if !has { if !has {
return false, 0, nil return false, 0, nil
} }
for _, r := range rights { for _, r := range permissions {
if r == right.MaxRight { if r == permission.MaxPermission {
return true, int(right.MaxRight), nil return true, int(permission.MaxPermission), nil
} }
} }
return false, 0, nil return false, 0, nil
} }
type projectRight struct { type projectPermission struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
MaxRight Right MaxPermission Permission
} }
func checkRightsForProjects(s *xorm.Session, u *user.User, projectIDs []int64) (projectRightMap map[int64]*projectRight, err error) { func checkPermissionsForProjects(s *xorm.Session, u *user.User, projectIDs []int64) (projectPermissionMap map[int64]*projectPermission, err error) {
projectRightMap = make(map[int64]*projectRight) projectPermissionMap = make(map[int64]*projectPermission)
if len(projectIDs) < 1 { if len(projectIDs) < 1 {
return return
@ -277,9 +278,9 @@ WITH RECURSIVE
ph.original_project_id, ph.original_project_id,
CASE CASE
WHEN p.owner_id = ? THEN 2 WHEN p.owner_id = ? THEN 2
WHEN COALESCE(ul.right, 0) > COALESCE(tl.right, 0) THEN ul.right WHEN COALESCE(ul.permission, 0) > COALESCE(tl.permission, 0) THEN ul.permission
ELSE COALESCE(tl.right, 0) ELSE COALESCE(tl.permission, 0)
END AS project_right, END AS project_permission,
CASE CASE
WHEN p.owner_id = ? THEN 1 -- Direct project ownership WHEN p.owner_id = ? THEN 1 -- Direct project ownership
ELSE ph.level + 1 -- Derived from parent project ELSE ph.level + 1 -- Derived from parent project
@ -293,12 +294,12 @@ WITH RECURSIVE
WHERE p.owner_id = ? OR ul.user_id = ? OR tm.user_id = ?) WHERE p.owner_id = ? OR ul.user_id = ? OR tm.user_id = ?)
SELECT ph.original_project_id AS id, SELECT ph.original_project_id AS id,
COALESCE(MAX(pp.project_right), -1) AS max_right COALESCE(MAX(pp.project_permission), -1) AS max_permission
FROM project_hierarchy ph FROM project_hierarchy ph
LEFT JOIN (SELECT *, LEFT JOIN (SELECT *,
ROW_NUMBER() OVER (PARTITION BY original_project_id ORDER BY priority) AS rn ROW_NUMBER() OVER (PARTITION BY original_project_id ORDER BY priority) AS rn
FROM project_permissions) pp ON ph.id = pp.id AND pp.rn = 1 FROM project_permissions) pp ON ph.id = pp.id AND pp.rn = 1
GROUP BY ph.original_project_id`, args...). GROUP BY ph.original_project_id`, args...).
Find(&projectRightMap) Find(&projectPermissionMap)
return return
} }

View File

@ -34,16 +34,16 @@ type TeamProject struct {
TeamID int64 `xorm:"bigint not null INDEX" json:"team_id" param:"team"` TeamID int64 `xorm:"bigint not null INDEX" json:"team_id" param:"team"`
// The project id. // The project id.
ProjectID int64 `xorm:"bigint not null INDEX" json:"-" param:"project"` ProjectID int64 `xorm:"bigint not null INDEX" json:"-" param:"project"`
// The right this team has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. // The permission this team has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.
Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` Permission Permission `xorm:"bigint INDEX not null default 0" json:"permission" valid:"length(0|2)" maximum:"2" default:"0"`
// A timestamp when this relation was created. You cannot change this value. // A timestamp when this relation was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
// A timestamp when this relation was last updated. You cannot change this value. // A timestamp when this relation was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"` Updated time.Time `xorm:"updated not null" json:"updated"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName makes beautiful table names // TableName makes beautiful table names
@ -51,10 +51,10 @@ func (*TeamProject) TableName() string {
return "team_projects" return "team_projects"
} }
// TeamWithRight represents a team, combined with rights. // TeamWithPermission represents a team, combined with permissions.
type TeamWithRight struct { type TeamWithPermission struct {
Team `xorm:"extends"` Team `xorm:"extends"`
Right Right `json:"right"` Permission Permission `json:"permission"`
} }
// Create creates a new team <-> project relation // Create creates a new team <-> project relation
@ -74,8 +74,8 @@ type TeamWithRight struct {
// @Router /projects/{id}/teams [put] // @Router /projects/{id}/teams [put]
func (tl *TeamProject) Create(s *xorm.Session, a web.Auth) (err error) { func (tl *TeamProject) Create(s *xorm.Session, a web.Auth) (err error) {
// Check if the rights are valid // Check if the permissions are valid
if err = tl.Right.isValid(); err != nil { if err = tl.Permission.isValid(); err != nil {
return return
} }
@ -177,8 +177,8 @@ func (tl *TeamProject) Delete(s *xorm.Session, _ web.Auth) (err error) {
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search teams by its name." // @Param s query string false "Search teams by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.TeamWithRight "The teams with their right." // @Success 200 {array} models.TeamWithPermission "The teams with their permission."
// @Failure 403 {object} web.HTTPError "No right to see the project." // @Failure 403 {object} web.HTTPError "No permission to see the project."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{id}/teams [get] // @Router /projects/{id}/teams [get]
func (tl *TeamProject) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { func (tl *TeamProject) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
@ -195,7 +195,7 @@ func (tl *TeamProject) ReadAll(s *xorm.Session, a web.Auth, search string, page
limit, start := getLimitFromPageIndex(page, perPage) limit, start := getLimitFromPageIndex(page, perPage)
// Get the teams // Get the teams
all := []*TeamWithRight{} all := []*TeamWithPermission{}
query := s. query := s.
Table("teams"). Table("teams").
Join("INNER", "team_projects", "team_id = teams.id"). Join("INNER", "team_projects", "team_id = teams.id").
@ -224,7 +224,7 @@ func (tl *TeamProject) ReadAll(s *xorm.Session, a web.Auth, search string, page
Join("INNER", "team_projects", "team_id = teams.id"). Join("INNER", "team_projects", "team_id = teams.id").
Where("team_projects.project_id = ?", tl.ProjectID). Where("team_projects.project_id = ?", tl.ProjectID).
Where("teams.name LIKE ?", "%"+search+"%"). Where("teams.name LIKE ?", "%"+search+"%").
Count(&TeamWithRight{}) Count(&TeamWithPermission{})
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -234,7 +234,7 @@ func (tl *TeamProject) ReadAll(s *xorm.Session, a web.Auth, search string, page
// Update updates a team <-> project relation // Update updates a team <-> project relation
// @Summary Update a team <-> project relation // @Summary Update a team <-> project relation
// @Description Update a team <-> project relation. Mostly used to update the right that team has. // @Description Update a team <-> project relation. Mostly used to update the permission that team has.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -249,14 +249,14 @@ func (tl *TeamProject) ReadAll(s *xorm.Session, a web.Auth, search string, page
// @Router /projects/{projectID}/teams/{teamID} [post] // @Router /projects/{projectID}/teams/{teamID} [post]
func (tl *TeamProject) Update(s *xorm.Session, _ web.Auth) (err error) { func (tl *TeamProject) Update(s *xorm.Session, _ web.Auth) (err error) {
// Check if the right is valid // Check if the permission is valid
if err := tl.Right.isValid(); err != nil { if err := tl.Permission.isValid(); err != nil {
return err return err
} }
_, err = s. _, err = s.
Where("project_id = ? AND team_id = ?", tl.ProjectID, tl.TeamID). Where("project_id = ? AND team_id = ?", tl.ProjectID, tl.TeamID).
Cols("right"). Cols("permission").
Update(tl) Update(tl)
if err != nil { if err != nil {
return err return err

View File

@ -60,9 +60,9 @@ func TestTeamProject_ReadAll(t *testing.T) {
}) })
t.Run("no access", func(t *testing.T) { t.Run("no access", func(t *testing.T) {
tl := TeamProject{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ProjectID: 5, ProjectID: 5,
Right: RightAdmin, Permission: PermissionAdmin,
} }
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
@ -80,7 +80,7 @@ func TestTeamProject_ReadAll(t *testing.T) {
teams, _, _, err := tl.ReadAll(s, u, "TEAM9", 1, 50) teams, _, _, err := tl.ReadAll(s, u, "TEAM9", 1, 50)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, reflect.Slice, reflect.TypeOf(teams).Kind()) assert.Equal(t, reflect.Slice, reflect.TypeOf(teams).Kind())
ts := teams.([]*TeamWithRight) ts := teams.([]*TeamWithPermission)
assert.Len(t, ts, 1) assert.Len(t, ts, 1)
assert.Equal(t, int64(9), ts[0].ID) assert.Equal(t, int64(9), ts[0].ID)
_ = s.Close() _ = s.Close()
@ -93,9 +93,9 @@ func TestTeamProject_Create(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamProject{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ProjectID: 1, ProjectID: 1,
Right: RightAdmin, Permission: PermissionAdmin,
} }
allowed, _ := tl.CanCreate(s, u) allowed, _ := tl.CanCreate(s, u)
assert.True(t, allowed) assert.True(t, allowed)
@ -106,33 +106,33 @@ func TestTeamProject_Create(t *testing.T) {
db.AssertExists(t, "team_projects", map[string]interface{}{ db.AssertExists(t, "team_projects", map[string]interface{}{
"team_id": 1, "team_id": 1,
"project_id": 1, "project_id": 1,
"right": RightAdmin, "permission": PermissionAdmin,
}, false) }, false)
}) })
t.Run("team already has access", func(t *testing.T) { t.Run("team already has access", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamProject{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ProjectID: 3, ProjectID: 3,
Right: RightAdmin, Permission: PermissionAdmin,
} }
err := tl.Create(s, u) err := tl.Create(s, u)
require.Error(t, err) require.Error(t, err)
assert.True(t, IsErrTeamAlreadyHasAccess(err)) assert.True(t, IsErrTeamAlreadyHasAccess(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("wrong rights", func(t *testing.T) { t.Run("wrong permissions", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamProject{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ProjectID: 1, ProjectID: 1,
Right: RightUnknown, Permission: PermissionUnknown,
} }
err := tl.Create(s, u) err := tl.Create(s, u)
require.Error(t, err) require.Error(t, err)
assert.True(t, IsErrInvalidRight(err)) assert.True(t, IsErrInvalidPermission(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("nonexistant team", func(t *testing.T) { t.Run("nonexistant team", func(t *testing.T) {
@ -208,14 +208,14 @@ func TestTeamProject_Delete(t *testing.T) {
func TestTeamProject_Update(t *testing.T) { func TestTeamProject_Update(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
TeamID int64 TeamID int64
ProjectID int64 ProjectID int64
Right Right Permission Permission
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
tests := []struct { tests := []struct {
name string name string
@ -226,36 +226,36 @@ func TestTeamProject_Update(t *testing.T) {
{ {
name: "Test Update Normally", name: "Test Update Normally",
fields: fields{ fields: fields{
ProjectID: 3, ProjectID: 3,
TeamID: 1, TeamID: 1,
Right: RightAdmin, Permission: PermissionAdmin,
}, },
}, },
{ {
name: "Test Update to write", name: "Test Update to write",
fields: fields{ fields: fields{
ProjectID: 3, ProjectID: 3,
TeamID: 1, TeamID: 1,
Right: RightWrite, Permission: PermissionWrite,
}, },
}, },
{ {
name: "Test Update to Read", name: "Test Update to Read",
fields: fields{ fields: fields{
ProjectID: 3, ProjectID: 3,
TeamID: 1, TeamID: 1,
Right: RightRead, Permission: PermissionRead,
}, },
}, },
{ {
name: "Test Update with invalid right", name: "Test Update with invalid permission",
fields: fields{ fields: fields{
ProjectID: 3, ProjectID: 3,
TeamID: 1, TeamID: 1,
Right: 500, Permission: 500,
}, },
wantErr: true, wantErr: true,
errType: IsErrInvalidRight, errType: IsErrInvalidPermission,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -264,14 +264,14 @@ func TestTeamProject_Update(t *testing.T) {
s := db.NewSession() s := db.NewSession()
tl := &TeamProject{ tl := &TeamProject{
ID: tt.fields.ID, ID: tt.fields.ID,
TeamID: tt.fields.TeamID, TeamID: tt.fields.TeamID,
ProjectID: tt.fields.ProjectID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Permission: tt.fields.Permission,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
err := tl.Update(s, &user.User{ID: 1}) err := tl.Update(s, &user.User{ID: 1})
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
@ -286,7 +286,7 @@ func TestTeamProject_Update(t *testing.T) {
db.AssertExists(t, "team_projects", map[string]interface{}{ db.AssertExists(t, "team_projects", map[string]interface{}{
"project_id": tt.fields.ProjectID, "project_id": tt.fields.ProjectID,
"team_id": tt.fields.TeamID, "team_id": tt.fields.TeamID,
"right": tt.fields.Right, "permission": tt.fields.Permission,
}, false) }, false)
} }
}) })

View File

@ -37,16 +37,16 @@ type ProjectUser struct {
UserID int64 `xorm:"bigint not null INDEX" json:"-"` UserID int64 `xorm:"bigint not null INDEX" json:"-"`
// The project id. // The project id.
ProjectID int64 `xorm:"bigint not null INDEX" json:"-" param:"project"` ProjectID int64 `xorm:"bigint not null INDEX" json:"-" param:"project"`
// The right this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. // The permission this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.
Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` Permission Permission `xorm:"bigint INDEX not null default 0" json:"permission" valid:"length(0|2)" maximum:"2" default:"0"`
// A timestamp when this relation was created. You cannot change this value. // A timestamp when this relation was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
// A timestamp when this relation was last updated. You cannot change this value. // A timestamp when this relation was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"` Updated time.Time `xorm:"updated not null" json:"updated"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName is the table name for ProjectUser // TableName is the table name for ProjectUser
@ -54,10 +54,10 @@ func (*ProjectUser) TableName() string {
return "users_projects" return "users_projects"
} }
// UserWithRight represents a user in combination with the right it can have on a project // UserWithPermission represents a user in combination with the permission it can have on a project
type UserWithRight struct { type UserWithPermission struct {
user.User `xorm:"extends"` user.User `xorm:"extends"`
Right Right `json:"right"` Permission Permission `json:"permission"`
} }
// Create creates a new project <-> user relation // Create creates a new project <-> user relation
@ -77,8 +77,8 @@ type UserWithRight struct {
// @Router /projects/{id}/users [put] // @Router /projects/{id}/users [put]
func (lu *ProjectUser) Create(s *xorm.Session, a web.Auth) (err error) { func (lu *ProjectUser) Create(s *xorm.Session, a web.Auth) (err error) {
// Check if the right is valid // Check if the permission is valid
if err := lu.Right.isValid(); err != nil { if err := lu.Permission.isValid(); err != nil {
return err return err
} }
@ -183,8 +183,8 @@ func (lu *ProjectUser) Delete(s *xorm.Session, _ web.Auth) (err error) {
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search users by its name." // @Param s query string false "Search users by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.UserWithRight "The users with the right they have." // @Success 200 {array} models.UserWithPermission "The users with the permission they have."
// @Failure 403 {object} web.HTTPError "No right to see the project." // @Failure 403 {object} web.HTTPError "No permission to see the project."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{id}/users [get] // @Router /projects/{id}/users [get]
func (lu *ProjectUser) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { func (lu *ProjectUser) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
@ -201,7 +201,7 @@ func (lu *ProjectUser) ReadAll(s *xorm.Session, a web.Auth, search string, page
limit, start := getLimitFromPageIndex(page, perPage) limit, start := getLimitFromPageIndex(page, perPage)
// Get all users // Get all users
all := []*UserWithRight{} all := []*UserWithPermission{}
query := s. query := s.
Join("INNER", "users_projects", "user_id = users.id"). Join("INNER", "users_projects", "user_id = users.id").
Where("users_projects.project_id = ?", lu.ProjectID). Where("users_projects.project_id = ?", lu.ProjectID).
@ -223,14 +223,14 @@ func (lu *ProjectUser) ReadAll(s *xorm.Session, a web.Auth, search string, page
Join("INNER", "users_projects", "user_id = users.id"). Join("INNER", "users_projects", "user_id = users.id").
Where("users_projects.project_id = ?", lu.ProjectID). Where("users_projects.project_id = ?", lu.ProjectID).
Where("users.username LIKE ?", "%"+search+"%"). Where("users.username LIKE ?", "%"+search+"%").
Count(&UserWithRight{}) Count(&UserWithPermission{})
return all, len(all), numberOfTotalItems, err return all, len(all), numberOfTotalItems, err
} }
// Update updates a user <-> project relation // Update updates a user <-> project relation
// @Summary Update a user <-> project relation // @Summary Update a user <-> project relation
// @Description Update a user <-> project relation. Mostly used to update the right that user has. // @Description Update a user <-> project relation. Mostly used to update the permission that user has.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -245,8 +245,8 @@ func (lu *ProjectUser) ReadAll(s *xorm.Session, a web.Auth, search string, page
// @Router /projects/{projectID}/users/{userID} [post] // @Router /projects/{projectID}/users/{userID} [post]
func (lu *ProjectUser) Update(s *xorm.Session, _ web.Auth) (err error) { func (lu *ProjectUser) Update(s *xorm.Session, _ web.Auth) (err error) {
// Check if the right is valid // Check if the permission is valid
if err := lu.Right.isValid(); err != nil { if err := lu.Permission.isValid(); err != nil {
return err return err
} }
@ -259,7 +259,7 @@ func (lu *ProjectUser) Update(s *xorm.Session, _ web.Auth) (err error) {
_, err = s. _, err = s.
Where("project_id = ? AND user_id = ?", lu.ProjectID, lu.UserID). Where("project_id = ? AND user_id = ?", lu.ProjectID, lu.UserID).
Cols("right"). Cols("permission").
Update(lu) Update(lu)
if err != nil { if err != nil {
return err return err

View File

@ -28,14 +28,14 @@ import (
func TestProjectUser_CanDoSomething(t *testing.T) { func TestProjectUser_CanDoSomething(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 UserID int64
ProjectID int64 ProjectID int64
Right Right Permission Permission
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
type args struct { type args struct {
a web.Auth a web.Auth
@ -67,7 +67,7 @@ func TestProjectUser_CanDoSomething(t *testing.T) {
want: map[string]bool{"CanCreate": false, "CanDelete": false, "CanUpdate": false}, want: map[string]bool{"CanCreate": false, "CanDelete": false, "CanUpdate": false},
}, },
{ {
name: "CanDoSomething where the user does not have the rights", name: "CanDoSomething where the user does not have the permissions",
fields: fields{ fields: fields{
ProjectID: 3, ProjectID: 3,
}, },
@ -83,14 +83,14 @@ func TestProjectUser_CanDoSomething(t *testing.T) {
s := db.NewSession() s := db.NewSession()
lu := &ProjectUser{ lu := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, UserID: tt.fields.UserID,
ProjectID: tt.fields.ProjectID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Permission: tt.fields.Permission,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
if got, _ := lu.CanCreate(s, tt.args.a); got != tt.want["CanCreate"] { if got, _ := lu.CanCreate(s, tt.args.a); got != tt.want["CanCreate"] {
t.Errorf("ProjectUser.CanCreate() = %v, want %v", got, tt.want["CanCreate"]) t.Errorf("ProjectUser.CanCreate() = %v, want %v", got, tt.want["CanCreate"])

View File

@ -32,15 +32,15 @@ import (
func TestProjectUser_Create(t *testing.T) { func TestProjectUser_Create(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 UserID int64
Username string Username string
ProjectID int64 ProjectID int64
Right Right Permission Permission
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
type args struct { type args struct {
a web.Auth a web.Auth
@ -69,14 +69,14 @@ func TestProjectUser_Create(t *testing.T) {
errType: IsErrUserAlreadyHasAccess, errType: IsErrUserAlreadyHasAccess,
}, },
{ {
name: "ListUsers Create with invalid right", name: "ListUsers Create with invalid permission",
fields: fields{ fields: fields{
Username: "user1", Username: "user1",
ProjectID: 2, ProjectID: 2,
Right: 500, Permission: 500,
}, },
wantErr: true, wantErr: true,
errType: IsErrInvalidRight, errType: IsErrInvalidPermission,
}, },
{ {
name: "ListUsers Create with inexisting project", name: "ListUsers Create with inexisting project",
@ -112,15 +112,15 @@ func TestProjectUser_Create(t *testing.T) {
s := db.NewSession() s := db.NewSession()
ul := &ProjectUser{ ul := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, UserID: tt.fields.UserID,
Username: tt.fields.Username, Username: tt.fields.Username,
ProjectID: tt.fields.ProjectID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Permission: tt.fields.Permission,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
err := ul.Create(s, tt.args.a) err := ul.Create(s, tt.args.a)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
@ -144,7 +144,7 @@ func TestProjectUser_Create(t *testing.T) {
} }
func TestProjectUser_ReadAll(t *testing.T) { func TestProjectUser_ReadAll(t *testing.T) {
user1Read := &UserWithRight{ user1Read := &UserWithPermission{
User: user.User{ User: user.User{
ID: 1, ID: 1,
Username: "user1", Username: "user1",
@ -157,9 +157,9 @@ func TestProjectUser_ReadAll(t *testing.T) {
Updated: testUpdatedTime, Updated: testUpdatedTime,
ExportFileID: 1, ExportFileID: 1,
}, },
Right: RightRead, Permission: PermissionRead,
} }
user2Read := &UserWithRight{ user2Read := &UserWithPermission{
User: user.User{ User: user.User{
ID: 2, ID: 2,
Username: "user2", Username: "user2",
@ -172,18 +172,18 @@ func TestProjectUser_ReadAll(t *testing.T) {
Created: testCreatedTime, Created: testCreatedTime,
Updated: testUpdatedTime, Updated: testUpdatedTime,
}, },
Right: RightRead, Permission: PermissionRead,
} }
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 UserID int64
ProjectID int64 ProjectID int64
Right Right Permission Permission
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
type args struct { type args struct {
search string search string
@ -206,7 +206,7 @@ func TestProjectUser_ReadAll(t *testing.T) {
args: args{ args: args{
a: &user.User{ID: 3}, a: &user.User{ID: 3},
}, },
want: []*UserWithRight{ want: []*UserWithPermission{
user1Read, user1Read,
user2Read, user2Read,
}, },
@ -231,7 +231,7 @@ func TestProjectUser_ReadAll(t *testing.T) {
a: &user.User{ID: 3}, a: &user.User{ID: 3},
search: "USER2", search: "USER2",
}, },
want: []*UserWithRight{ want: []*UserWithPermission{
user2Read, user2Read,
}, },
}, },
@ -242,14 +242,14 @@ func TestProjectUser_ReadAll(t *testing.T) {
s := db.NewSession() s := db.NewSession()
ul := &ProjectUser{ ul := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, UserID: tt.fields.UserID,
ProjectID: tt.fields.ProjectID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Permission: tt.fields.Permission,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
got, _, _, err := ul.ReadAll(s, tt.args.a, tt.args.search, tt.args.page, 50) got, _, _, err := ul.ReadAll(s, tt.args.a, tt.args.search, tt.args.page, 50)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
@ -268,14 +268,14 @@ func TestProjectUser_ReadAll(t *testing.T) {
func TestProjectUser_Update(t *testing.T) { func TestProjectUser_Update(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
Username string Username string
ProjectID int64 ProjectID int64
Right Right Permission Permission
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
tests := []struct { tests := []struct {
name string name string
@ -286,36 +286,36 @@ func TestProjectUser_Update(t *testing.T) {
{ {
name: "Test Update Normally", name: "Test Update Normally",
fields: fields{ fields: fields{
ProjectID: 3, ProjectID: 3,
Username: "user1", Username: "user1",
Right: RightAdmin, Permission: PermissionAdmin,
}, },
}, },
{ {
name: "Test Update to write", name: "Test Update to write",
fields: fields{ fields: fields{
ProjectID: 3, ProjectID: 3,
Username: "user1", Username: "user1",
Right: RightWrite, Permission: PermissionWrite,
}, },
}, },
{ {
name: "Test Update to Read", name: "Test Update to Read",
fields: fields{ fields: fields{
ProjectID: 3, ProjectID: 3,
Username: "user1", Username: "user1",
Right: RightRead, Permission: PermissionRead,
}, },
}, },
{ {
name: "Test Update with invalid right", name: "Test Update with invalid permission",
fields: fields{ fields: fields{
ProjectID: 3, ProjectID: 3,
Username: "user1", Username: "user1",
Right: 500, Permission: 500,
}, },
wantErr: true, wantErr: true,
errType: IsErrInvalidRight, errType: IsErrInvalidPermission,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -324,14 +324,14 @@ func TestProjectUser_Update(t *testing.T) {
s := db.NewSession() s := db.NewSession()
lu := &ProjectUser{ lu := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
Username: tt.fields.Username, Username: tt.fields.Username,
ProjectID: tt.fields.ProjectID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Permission: tt.fields.Permission,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
err := lu.Update(s, &user.User{ID: 1}) err := lu.Update(s, &user.User{ID: 1})
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
@ -348,7 +348,7 @@ func TestProjectUser_Update(t *testing.T) {
db.AssertExists(t, "users_projects", map[string]interface{}{ db.AssertExists(t, "users_projects", map[string]interface{}{
"project_id": tt.fields.ProjectID, "project_id": tt.fields.ProjectID,
"user_id": lu.UserID, "user_id": lu.UserID,
"right": tt.fields.Right, "permission": tt.fields.Permission,
}, false) }, false)
} }
}) })
@ -357,15 +357,15 @@ func TestProjectUser_Update(t *testing.T) {
func TestProjectUser_Delete(t *testing.T) { func TestProjectUser_Delete(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
Username string Username string
UserID int64 UserID int64
ProjectID int64 ProjectID int64
Right Right Permission Permission
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
tests := []struct { tests := []struct {
name string name string
@ -406,14 +406,14 @@ func TestProjectUser_Delete(t *testing.T) {
s := db.NewSession() s := db.NewSession()
lu := &ProjectUser{ lu := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
Username: tt.fields.Username, Username: tt.fields.Username,
ProjectID: tt.fields.ProjectID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Permission: tt.fields.Permission,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
err := lu.Delete(s, &user.User{ID: 1}) err := lu.Delete(s, &user.User{ID: 1})
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {

View File

@ -157,8 +157,8 @@ type ProjectView struct {
// A timestamp when this reaction was created. You cannot change this value. // A timestamp when this reaction was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
func (pv *ProjectView) TableName() string { func (pv *ProjectView) TableName() string {

View File

@ -53,8 +53,8 @@ type Reaction struct {
// A timestamp when this reaction was created. You cannot change this value. // A timestamp when this reaction was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
func (*Reaction) TableName() string { func (*Reaction) TableName() string {

View File

@ -53,8 +53,8 @@ type SavedFilter struct {
// A timestamp when this filter was last updated. You cannot change this value. // A timestamp when this filter was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"` Updated time.Time `xorm:"updated not null" json:"updated"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName returns a better table name for saved filters // TableName returns a better table name for saved filters
@ -170,7 +170,7 @@ func GetSavedFilterSimpleByID(s *xorm.Session, id int64) (sf *SavedFilter, err e
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /filters/{id} [get] // @Router /filters/{id} [get]
func (sf *SavedFilter) ReadOne(s *xorm.Session, _ web.Auth) error { func (sf *SavedFilter) ReadOne(s *xorm.Session, _ web.Auth) error {
// s already contains almost the full saved filter from the rights check, we only need to add the user // s already contains almost the full saved filter from the permissions check, we only need to add the user
u, err := user.GetUserByID(s, sf.OwnerID) u, err := user.GetUserByID(s, sf.OwnerID)
sf.Owner = u sf.Owner = u
return err return err

View File

@ -21,25 +21,25 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// CanRead checks if a user has the right to read a saved filter // CanRead checks if a user has the permission to read a saved filter
func (sf *SavedFilter) CanRead(s *xorm.Session, auth web.Auth) (bool, int, error) { func (sf *SavedFilter) CanRead(s *xorm.Session, auth web.Auth) (bool, int, error) {
can, err := sf.canDoFilter(s, auth) can, err := sf.canDoFilter(s, auth)
return can, int(RightAdmin), err return can, int(PermissionAdmin), err
} }
// CanDelete checks if a user has the right to delete a saved filter // CanDelete checks if a user has the permission to delete a saved filter
func (sf *SavedFilter) CanDelete(s *xorm.Session, auth web.Auth) (bool, error) { func (sf *SavedFilter) CanDelete(s *xorm.Session, auth web.Auth) (bool, error) {
return sf.canDoFilter(s, auth) return sf.canDoFilter(s, auth)
} }
// CanUpdate checks if a user has the right to update a saved filter // CanUpdate checks if a user has the permission to update a saved filter
func (sf *SavedFilter) CanUpdate(s *xorm.Session, auth web.Auth) (bool, error) { func (sf *SavedFilter) CanUpdate(s *xorm.Session, auth web.Auth) (bool, error) {
// A normal check would replace the passed struct which in our case would override the values we want to update. // A normal check would replace the passed struct which in our case would override the values we want to update.
sff := &SavedFilter{ID: sf.ID} sff := &SavedFilter{ID: sf.ID}
return sff.canDoFilter(s, auth) return sff.canDoFilter(s, auth)
} }
// CanCreate checks if a user has the right to update a saved filter // CanCreate checks if a user has the permission to update a saved filter
func (sf *SavedFilter) CanCreate(_ *xorm.Session, auth web.Auth) (bool, error) { func (sf *SavedFilter) CanCreate(_ *xorm.Session, auth web.Auth) (bool, error) {
if _, is := auth.(*LinkSharing); is { if _, is := auth.(*LinkSharing); is {
return false, nil return false, nil
@ -48,7 +48,7 @@ func (sf *SavedFilter) CanCreate(_ *xorm.Session, auth web.Auth) (bool, error) {
return true, nil return true, nil
} }
// Helper function to check saved filter rights sind they all have the same logic // Helper function to check saved filter permissions sind they all have the same logic
func (sf *SavedFilter) canDoFilter(s *xorm.Session, auth web.Auth) (can bool, err error) { func (sf *SavedFilter) canDoFilter(s *xorm.Session, auth web.Auth) (can bool, err error) {
// Link shares can't view or modify saved filters, therefore we can error out right away // Link shares can't view or modify saved filters, therefore we can error out right away
if _, is := auth.(*LinkSharing); is { if _, is := auth.(*LinkSharing); is {

View File

@ -191,7 +191,7 @@ func TestSavedFilter_Delete(t *testing.T) {
}) })
} }
func TestSavedFilter_Rights(t *testing.T) { func TestSavedFilter_Permissions(t *testing.T) {
user1 := &user.User{ID: 1} user1 := &user.User{ID: 1}
user2 := &user.User{ID: 2} user2 := &user.User{ID: 2}
ls := &LinkSharing{ID: 1} ls := &LinkSharing{ID: 1}
@ -216,9 +216,9 @@ func TestSavedFilter_Rights(t *testing.T) {
ID: 1, ID: 1,
Title: "Lorem", Title: "Lorem",
} }
can, maxRight, err := sf.CanRead(s, user1) can, maxPermission, err := sf.CanRead(s, user1)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, int(RightAdmin), maxRight) assert.Equal(t, int(PermissionAdmin), maxPermission)
assert.True(t, can) assert.True(t, can)
}) })
t.Run("not owner", func(t *testing.T) { t.Run("not owner", func(t *testing.T) {

View File

@ -109,8 +109,8 @@ type Subscription struct {
// A timestamp when this subscription was created. You cannot change this value. // A timestamp when this subscription was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
type SubscriptionWithUser struct { type SubscriptionWithUser struct {
@ -145,7 +145,7 @@ func (sb *Subscription) TableName() string {
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /subscriptions/{entity}/{entityID} [put] // @Router /subscriptions/{entity}/{entityID} [put]
func (sb *Subscription) Create(s *xorm.Session, auth web.Auth) (err error) { func (sb *Subscription) Create(s *xorm.Session, auth web.Auth) (err error) {
// Rights method already does the validation of the entity type, so we don't need to do that here // Permissions method already does the validation of the entity type, so we don't need to do that here
sb.ID = 0 sb.ID = 0
sb.UserID = auth.GetID() sb.UserID = auth.GetID()

View File

@ -136,7 +136,7 @@ func TestSubscription_Create(t *testing.T) {
assert.True(t, IsErrTaskDoesNotExist(err)) assert.True(t, IsErrTaskDoesNotExist(err))
assert.False(t, can) assert.False(t, can)
}) })
t.Run("no rights to see project", func(t *testing.T) { t.Run("no permissions to see project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
@ -151,7 +151,7 @@ func TestSubscription_Create(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.False(t, can) assert.False(t, can)
}) })
t.Run("no rights to see task", func(t *testing.T) { t.Run("no permissions to see task", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()

View File

@ -35,8 +35,8 @@ type TaskAssginee struct {
UserID int64 `xorm:"bigint INDEX not null" json:"user_id" param:"user"` UserID int64 `xorm:"bigint INDEX not null" json:"user_id" param:"user"`
Created time.Time `xorm:"created not null"` Created time.Time `xorm:"created not null"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName makes a pretty table name // TableName makes a pretty table name
@ -355,8 +355,8 @@ type BulkAssignees struct {
Assignees []*user.User `json:"assignees"` Assignees []*user.User `json:"assignees"`
TaskID int64 `json:"-" param:"projecttask"` TaskID int64 `json:"-" param:"projecttask"`
web.CRUDable `json:"-"` web.CRUDable `json:"-"`
web.Rights `json:"-"` web.Permissions `json:"-"`
} }
// Create adds new assignees to a task // Create adds new assignees to a task

View File

@ -48,8 +48,8 @@ type TaskAttachment struct {
Created time.Time `xorm:"created" json:"created"` Created time.Time `xorm:"created" json:"created"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName returns the table name for task attachments // TableName returns the table name for task attachments

View File

@ -197,7 +197,7 @@ func TestTaskAttachment_Delete(t *testing.T) {
}) })
} }
func TestTaskAttachment_Rights(t *testing.T) { func TestTaskAttachment_Permissions(t *testing.T) {
u := &user.User{ID: 1} u := &user.User{ID: 1}
t.Run("Can Read", func(t *testing.T) { t.Run("Can Read", func(t *testing.T) {
t.Run("Allowed", func(t *testing.T) { t.Run("Allowed", func(t *testing.T) {

View File

@ -60,8 +60,8 @@ type TaskCollection struct {
isSavedFilter bool isSavedFilter bool
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
type TaskCollectionExpandable string type TaskCollectionExpandable string

View File

@ -661,8 +661,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Expand []TaskCollectionExpandable Expand []TaskCollectionExpandable
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Permissions web.Permissions
} }
type args struct { type args struct {
search string search string
@ -1644,8 +1644,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Expand: tt.fields.Expand, Expand: tt.fields.Expand,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Permissions: tt.fields.Permissions,
} }
got, _, _, err := lt.ReadAll(s, tt.args.a, tt.args.search, tt.args.page, 50) got, _, _, err := lt.ReadAll(s, tt.args.a, tt.args.search, tt.args.page, 50)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {

View File

@ -41,8 +41,8 @@ type TaskComment struct {
Created time.Time `xorm:"created" json:"created"` Created time.Time `xorm:"created" json:"created"`
Updated time.Time `xorm:"updated" json:"updated"` Updated time.Time `xorm:"updated" json:"updated"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName holds the table name for the task comments table // TableName holds the table name for the task comments table

View File

@ -41,8 +41,8 @@ type TaskPosition struct {
// endpoint, otherwise they will always be 0. To update them, take a look at the Task Position endpoint. // endpoint, otherwise they will always be 0. To update them, take a look at the Task Position endpoint.
Position float64 `xorm:"double not null" json:"position"` Position float64 `xorm:"double not null" json:"position"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
func (tp *TaskPosition) TableName() string { func (tp *TaskPosition) TableName() string {

View File

@ -95,8 +95,8 @@ type TaskRelation struct {
// A timestamp when this label was created. You cannot change this value. // A timestamp when this label was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
// TableName holds the table name for the task relation table // TableName holds the table name for the task relation table
@ -188,7 +188,7 @@ func checkTaskRelationCycle(s *xorm.Session, relation *TaskRelation, otherTaskID
// Create creates a new task relation // Create creates a new task relation
// @Summary Create a new relation between two tasks // @Summary Create a new relation between two tasks
// @Description Creates a new relation between two tasks. The user needs to have update rights on the base task and at least read rights on the other task. Both tasks do not need to be on the same project. Take a look at the docs for available task relation kinds. // @Description Creates a new relation between two tasks. The user needs to have update permissions on the base task and at least read permissions on the other task. Both tasks do not need to be on the same project. Take a look at the docs for available task relation kinds.
// @tags task // @tags task
// @Accept json // @Accept json
// @Produce json // @Produce json

View File

@ -347,7 +347,7 @@ func TestTaskRelation_CanCreate(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.True(t, can) assert.True(t, can)
}) })
t.Run("No update rights on base task", func(t *testing.T) { t.Run("No update permissions on base task", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
@ -361,7 +361,7 @@ func TestTaskRelation_CanCreate(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.False(t, can) assert.False(t, can)
}) })
t.Run("No update rights on base task, but read rights", func(t *testing.T) { t.Run("No update permissions on base task, but read permissions", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
@ -375,7 +375,7 @@ func TestTaskRelation_CanCreate(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.False(t, can) assert.False(t, can)
}) })
t.Run("No read rights on other task", func(t *testing.T) { t.Run("No read permissions on other task", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()

View File

@ -141,8 +141,8 @@ type Task struct {
CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"` CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"`
CreatedByID int64 `xorm:"bigint not null" json:"-"` // ID of the user who put that task on the project CreatedByID int64 `xorm:"bigint not null" json:"-"` // ID of the user who put that task on the project
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Permissions `xorm:"-" json:"-"`
} }
type TaskWithComments struct { type TaskWithComments struct {
@ -1184,7 +1184,7 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
// //
// Maybe FIXME: // Maybe FIXME:
// I've disabled this for now, because it requires significant changes in the way we do updates (using the // I've disabled this for now, because it requires significant changes in the way we do updates (using the
// Update() function. We need a user object in updateTaskLabels to check if the user has the right to see // Update() function. We need a user object in updateTaskLabels to check if the user has the permission to see
// the label it is currently adding. To do this, we'll need to update the webhandler to let it pass the current // the label it is currently adding. To do this, we'll need to update the webhandler to let it pass the current
// user object (like it's already the case with the create method). However when we change it, that'll break // user object (like it's already the case with the create method). However when we change it, that'll break
// a lot of existing code which we'll then need to refactor. // a lot of existing code which we'll then need to refactor.

View File

@ -26,12 +26,12 @@ func (t *Task) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
return t.canDoTask(s, a) return t.canDoTask(s, a)
} }
// CanUpdate determines if a user has the right to update a project task // CanUpdate determines if a user has the permission to update a project task
func (t *Task) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { func (t *Task) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
return t.canDoTask(s, a) return t.canDoTask(s, a)
} }
// CanCreate determines if a user has the right to create a project task // CanCreate determines if a user has the permission to create a project task
func (t *Task) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { func (t *Task) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
// A user can do a task if he has write acces to its project // A user can do a task if he has write acces to its project
l := &Project{ID: t.ProjectID} l := &Project{ID: t.ProjectID}
@ -39,7 +39,7 @@ func (t *Task) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
} }
// CanRead determines if a user can read a task // CanRead determines if a user can read a task
func (t *Task) CanRead(s *xorm.Session, a web.Auth) (canRead bool, maxRight int, err error) { func (t *Task) CanRead(s *xorm.Session, a web.Auth) (canRead bool, maxPermission int, err error) {
expand := t.Expand expand := t.Expand
// Get the task, error out if it doesn't exist // Get the task, error out if it doesn't exist
*t, err = GetTaskByIDSimple(s, t.ID) *t, err = GetTaskByIDSimple(s, t.ID)
@ -67,7 +67,7 @@ func (t *Task) canDoTask(s *xorm.Session, a web.Auth) (bool, error) {
return false, err return false, err
} }
// Check if we're moving the task into a different project to check if the user has sufficient rights for that on the new project // Check if we're moving the task into a different project to check if the user has sufficient permissions for that on the new project
if t.ProjectID != 0 && t.ProjectID != ot.ProjectID { if t.ProjectID != 0 && t.ProjectID != ot.ProjectID {
newProject := &Project{ID: t.ProjectID} newProject := &Project{ID: t.ProjectID}
can, err := newProject.CanWrite(s, a) can, err := newProject.CanWrite(s, a)

View File

@ -36,7 +36,7 @@ func TestTask_Create(t *testing.T) {
Email: "user1@example.com", Email: "user1@example.com",
} }
// We only test creating a task here, the rights are all well tested in the web tests. // We only test creating a task here, the permissions are all well tested in the web tests.
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)

View File

@ -132,7 +132,7 @@ func (tm *TeamMember) MembershipExists(s *xorm.Session) (exists bool, err error)
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "Team ID" // @Param id path int true "Team ID"
// @Param userID path int true "User ID" // @Param userID path int true "User ID"
// @Success 200 {object} models.Message "The member right was successfully changed." // @Success 200 {object} models.Message "The member permission was successfully changed."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /teams/{id}/members/{userID}/admin [post] // @Router /teams/{id}/members/{userID}/admin [post]
func (tm *TeamMember) Update(s *xorm.Session, _ web.Auth) (err error) { func (tm *TeamMember) Update(s *xorm.Session, _ web.Auth) (err error) {
@ -143,7 +143,7 @@ func (tm *TeamMember) Update(s *xorm.Session, _ web.Auth) (err error) {
} }
tm.UserID = user.ID tm.UserID = user.ID
// Get the full member object and change the admin right // Get the full member object and change the admin permission
ttm := &TeamMember{} ttm := &TeamMember{}
_, err = s. _, err = s.
Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID). Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).
@ -158,6 +158,6 @@ func (tm *TeamMember) Update(s *xorm.Session, _ web.Auth) (err error) {
Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID). Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).
Cols("admin"). Cols("admin").
Update(ttm) Update(ttm)
tm.Admin = ttm.Admin // Since we're returning the updated rights object tm.Admin = ttm.Admin // Since we're returning the updated permissions object
return return
} }

View File

@ -39,7 +39,7 @@ func (tm *TeamMember) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
return tm.IsAdmin(s, a) return tm.IsAdmin(s, a)
} }
// CanUpdate checks if the user can modify a team member's right // CanUpdate checks if the user can modify a team member's permission
func (tm *TeamMember) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { func (tm *TeamMember) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
return tm.IsAdmin(s, a) return tm.IsAdmin(s, a)
} }

Some files were not shown because too many files have changed in this diff Show More