Ajout d'une query GraphQL pour vérifier les autorisations côté serveur
- Intégration des vérifications de droits sur la page de création/modification des groupes de travail
This commit is contained in:
parent
3ef495445a
commit
9c6ebae9bc
|
@ -1,12 +1,13 @@
|
||||||
import React, { useState, ChangeEvent, useEffect } from 'react';
|
import React, { useState, ChangeEvent, useEffect } from 'react';
|
||||||
import { Workgroup } from '../../types/workgroup';
|
import { Workgroup } from '../../types/workgroup';
|
||||||
|
import { useIsAuthorized } from '../../gql/queries/authorization';
|
||||||
|
|
||||||
export interface InfoFormProps {
|
export interface InfoFormProps {
|
||||||
workgroup: Workgroup
|
workgroup: Workgroup
|
||||||
onChange?: (workgroup: Workgroup) => void
|
onChange?: (workgroup: Workgroup) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InfoForm({ workgroup, onChange }: InfoFormProps) {
|
export function InfoForm({ workgroup, onChange }: InfoFormProps) {
|
||||||
const [ state, setState ] = useState({
|
const [ state, setState ] = useState({
|
||||||
changed: false,
|
changed: false,
|
||||||
workgroup: {
|
workgroup: {
|
||||||
|
@ -17,6 +18,15 @@ export function InfoForm({ workgroup, onChange }: InfoFormProps) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { isAuthorized } = useIsAuthorized({
|
||||||
|
variables: {
|
||||||
|
action: 'update',
|
||||||
|
object: {
|
||||||
|
workgroupId: state.workgroup.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, state.workgroup.id === '' ? true : false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setState({
|
setState({
|
||||||
changed: false,
|
changed: false,
|
||||||
|
@ -60,7 +70,8 @@ export function InfoForm({ workgroup, onChange }: InfoFormProps) {
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label className="label">Nom du groupe</label>
|
<label className="label">Nom du groupe</label>
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<input type="text" className="input" value={state.workgroup.name}
|
<input type="text" className="input" value={state.workgroup.name}
|
||||||
|
disabled={!isAuthorized}
|
||||||
onChange={onWorkgroupAttrChange.bind(null, "name")} />
|
onChange={onWorkgroupAttrChange.bind(null, "name")} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,7 +96,7 @@ export function InfoForm({ workgroup, onChange }: InfoFormProps) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
<div className="buttons is-right">
|
<div className="buttons is-right">
|
||||||
<button disabled={!state.changed}
|
<button disabled={!state.changed || !isAuthorized}
|
||||||
className="button is-success" onClick={onSaveClick}>
|
className="button is-success" onClick={onSaveClick}>
|
||||||
<span>Enregistrer</span>
|
<span>Enregistrer</span>
|
||||||
<span className="icon"><i className="fa fa-save"></i></span>
|
<span className="icon"><i className="fa fa-save"></i></span>
|
||||||
|
|
|
@ -20,9 +20,11 @@ export function WorkgroupPage() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const userProfileQuery = useUserProfileQuery();
|
const userProfileQuery = useUserProfileQuery();
|
||||||
|
|
||||||
const [ joinWorkgroup, joinWorkgroupMutation ] = useJoinWorkgroupMutation();
|
const [ joinWorkgroup, joinWorkgroupMutation ] = useJoinWorkgroupMutation();
|
||||||
const [ leaveWorkgroup, leaveWorkgroupMutation ] = useLeaveWorkgroupMutation();
|
const [ leaveWorkgroup, leaveWorkgroupMutation ] = useLeaveWorkgroupMutation();
|
||||||
const [ closeWorkgroup, closeWorkgroupMutation ] = useCloseWorkgroupMutation();
|
const [ closeWorkgroup, closeWorkgroupMutation ] = useCloseWorkgroupMutation();
|
||||||
|
|
||||||
const [ state, setState ] = useState({
|
const [ state, setState ] = useState({
|
||||||
userProfileId: '',
|
userProfileId: '',
|
||||||
workgroup: {
|
workgroup: {
|
||||||
|
|
|
@ -34,13 +34,17 @@ export const client = new ApolloClient<any>({
|
||||||
|
|
||||||
function mergeArrayByField<T>(fieldName: string) {
|
function mergeArrayByField<T>(fieldName: string) {
|
||||||
return (existing: T[] = [], incoming: T[], { readField, mergeObjects }) => {
|
return (existing: T[] = [], incoming: T[], { readField, mergeObjects }) => {
|
||||||
|
if (incoming.length === 0) return [];
|
||||||
|
|
||||||
const merged: any[] = existing ? existing.slice(0) : [];
|
const merged: any[] = existing ? existing.slice(0) : [];
|
||||||
|
|
||||||
const objectFieldToIndex: Record<string, number> = Object.create(null);
|
const objectFieldToIndex: Record<string, number> = Object.create(null);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.forEach((obj, index) => {
|
existing.forEach((obj, index) => {
|
||||||
objectFieldToIndex[readField(fieldName, obj)] = index;
|
objectFieldToIndex[readField(fieldName, obj)] = index;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
incoming.forEach(obj => {
|
incoming.forEach(obj => {
|
||||||
const field = readField(fieldName, obj);
|
const field = readField(fieldName, obj);
|
||||||
const index = objectFieldToIndex[field];
|
const index = objectFieldToIndex[field];
|
||||||
|
@ -51,6 +55,7 @@ function mergeArrayByField<T>(fieldName: string) {
|
||||||
merged.push(obj);
|
merged.push(obj);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { gql, useQuery, useMutation } from '@apollo/client';
|
import { gql, useQuery, useMutation, FetchResult } from '@apollo/client';
|
||||||
import { QUERY_WORKGROUP } from '../queries/workgroups';
|
import { QUERY_WORKGROUP } from '../queries/workgroups';
|
||||||
|
import { QUERY_IS_AUTHORIZED } from '../queries/authorization';
|
||||||
|
|
||||||
export const MUTATION_UPDATE_WORKGROUP = gql`
|
export const MUTATION_UPDATE_WORKGROUP = gql`
|
||||||
mutation updateWorkgroup($workgroupId: ID!, $changes: WorkgroupChanges!) {
|
mutation updateWorkgroup($workgroupId: ID!, $changes: WorkgroupChanges!) {
|
||||||
|
@ -57,7 +58,19 @@ mutation joinWorkgroup($workgroupId: ID!) {
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
export function useJoinWorkgroupMutation() {
|
export function useJoinWorkgroupMutation() {
|
||||||
return useMutation(MUTATION_JOIN_WORKGROUP);
|
return useMutation(MUTATION_JOIN_WORKGROUP, {
|
||||||
|
refetchQueries: ({ data }: FetchResult) => {
|
||||||
|
return [{
|
||||||
|
query: QUERY_IS_AUTHORIZED,
|
||||||
|
variables: {
|
||||||
|
action: 'update',
|
||||||
|
object: {
|
||||||
|
workgroupId: data.joinWorkgroup.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const MUTATION_LEAVE_WORKGROUP = gql`
|
const MUTATION_LEAVE_WORKGROUP = gql`
|
||||||
|
@ -76,7 +89,27 @@ mutation leaveWorkgroup($workgroupId: ID!) {
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
export function useLeaveWorkgroupMutation() {
|
export function useLeaveWorkgroupMutation() {
|
||||||
return useMutation(MUTATION_LEAVE_WORKGROUP);
|
return useMutation(MUTATION_LEAVE_WORKGROUP, {
|
||||||
|
refetchQueries: ({ data }: FetchResult) => {
|
||||||
|
return [{
|
||||||
|
query: QUERY_WORKGROUP,
|
||||||
|
variables: {
|
||||||
|
filter: {
|
||||||
|
ids: [data.leaveWorkgroup.id],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: QUERY_IS_AUTHORIZED,
|
||||||
|
variables: {
|
||||||
|
action: 'update',
|
||||||
|
object: {
|
||||||
|
workgroupId: data.leaveWorkgroup.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const MUTATION_CLOSE_WORKGROUP = gql`
|
const MUTATION_CLOSE_WORKGROUP = gql`
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { gql, useQuery } from '@apollo/client';
|
||||||
|
import { useGraphQLData } from './helper';
|
||||||
|
|
||||||
|
export const QUERY_IS_AUTHORIZED = gql`
|
||||||
|
query isAuthorized($action: String!, $object: AuthorizationObject!) {
|
||||||
|
isAuthorized(action: $action, object: $object)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function useIsAuthorizedQuery(options = {}) {
|
||||||
|
return useQuery(QUERY_IS_AUTHORIZED, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIsAuthorized(options = {}, defaultValue = false) {
|
||||||
|
const { data, loading, error } = useGraphQLData<boolean>(
|
||||||
|
QUERY_IS_AUTHORIZED, 'isAuthorized', defaultValue, options
|
||||||
|
);
|
||||||
|
return { isAuthorized: data, loading, error };
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
|
errs "github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleIsAuthorized(ctx context.Context, action string, obj model.AuthorizationObject) (bool, error) {
|
||||||
|
db, err := getDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var object interface{}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case obj.WorkgroupID != nil:
|
||||||
|
repo := model.NewWorkgroupRepository(db)
|
||||||
|
|
||||||
|
workgroup, err := repo.Find(ctx, *obj.WorkgroupID)
|
||||||
|
if err != nil {
|
||||||
|
return false, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
object = workgroup
|
||||||
|
|
||||||
|
case obj.DecisionSupportFileID != nil:
|
||||||
|
repo := model.NewDSFRepository(db)
|
||||||
|
|
||||||
|
dsf, err := repo.Find(ctx, *obj.DecisionSupportFileID)
|
||||||
|
if err != nil {
|
||||||
|
return false, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
object = dsf
|
||||||
|
|
||||||
|
case obj.UserID != nil:
|
||||||
|
repo := model.NewUserRepository(db)
|
||||||
|
|
||||||
|
user, err := repo.Find(ctx, *obj.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return false, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
object = user
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false, errs.WithStack(ErrInvalidInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
authorized, err := isAuthorized(ctx, object, model.Action(action))
|
||||||
|
if err != nil {
|
||||||
|
return false, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorized, nil
|
||||||
|
}
|
|
@ -3,5 +3,6 @@ package graph
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrForbidden = errors.New("forbidden")
|
ErrForbidden = errors.New("forbidden")
|
||||||
|
ErrInvalidInput = errors.New("invalid input")
|
||||||
)
|
)
|
||||||
|
|
|
@ -38,8 +38,15 @@ input DecisionSupportFileFilter {
|
||||||
ids: [ID]
|
ids: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input AuthorizationObject {
|
||||||
|
workgroupId: ID
|
||||||
|
userId: ID
|
||||||
|
decisionSupportFileId: ID
|
||||||
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
userProfile: User
|
userProfile: User
|
||||||
workgroups(filter: WorkgroupsFilter): [Workgroup]!
|
workgroups(filter: WorkgroupsFilter): [Workgroup]!
|
||||||
decisionSupportFiles(filter: DecisionSupportFileFilter): [DecisionSupportFile]!
|
decisionSupportFiles(filter: DecisionSupportFileFilter): [DecisionSupportFile]!
|
||||||
|
isAuthorized(action: String!, object: AuthorizationObject!): Boolean!
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,10 @@ func (r *queryResolver) DecisionSupportFiles(ctx context.Context, filter *model1
|
||||||
return handleDecisionSupportFiles(ctx, filter)
|
return handleDecisionSupportFiles(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) IsAuthorized(ctx context.Context, action string, object model1.AuthorizationObject) (bool, error) {
|
||||||
|
return handleIsAuthorized(ctx, action, object)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *userResolver) ID(ctx context.Context, obj *model1.User) (string, error) {
|
func (r *userResolver) ID(ctx context.Context, obj *model1.User) (string, error) {
|
||||||
return strconv.FormatUint(uint64(obj.ID), 10), nil
|
return strconv.FormatUint(uint64(obj.ID), 10), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
errs "github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRepository struct {
|
type UserRepository struct {
|
||||||
|
@ -68,6 +69,17 @@ func (r *UserRepository) UpdateUserByEmail(ctx context.Context, email string, ch
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) Find(ctx context.Context, id string) (*User, error) {
|
||||||
|
user := &User{}
|
||||||
|
query := r.db.Model(user).Where("id = ?", id)
|
||||||
|
|
||||||
|
if err := query.First(&user).Error; err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewUserRepository(db *gorm.DB) *UserRepository {
|
func NewUserRepository(db *gorm.DB) *UserRepository {
|
||||||
return &UserRepository{db}
|
return &UserRepository{db}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue