Skip to content

Commit

Permalink
Improvements Security logs (#1614)
Browse files Browse the repository at this point in the history
* Feedback:
- Search the User on authentication failure

* Improvement - WIP:
- Order log by DESC

* Review:
- Use ObjectManager instead of UserRepository
- Remove previously created function and use findByName instead

* Improvement logs security - WIP:
- Add a tab for the current user with his security logs

* Improvement logs security:
- add list of logs from current user

* Review :
- Change function name
- Remove useless finder's property
- Initialize the default sort in UI
  • Loading branch information
Zowac committed Mar 22, 2021
1 parent 34bfe6e commit 093c47a
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 9 deletions.
8 changes: 4 additions & 4 deletions src/main/core/API/Finder/Log/SecurityLogFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ public function configureQueryBuilder(QueryBuilder $qb, array $searches = [], ar
{
foreach ($searches as $filterName => $filterValue) {
switch ($filterName) {
case 'workspace':
$qb->leftJoin('obj.workspace', 'w');
$qb->andWhere('w.uuid = :workspaceId');
$qb->setParameter('workspaceId', $filterValue);
case 'user':
$qb->leftJoin('obj.doer', 'd');
$qb->andWhere('d.uuid = :id');
$qb->setParameter('id', $filterValue);
break;

default:
Expand Down
36 changes: 34 additions & 2 deletions src/main/core/Controller/APINew/Log/SecurityLogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,27 @@
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

/**
* @Route("/log/security")
*/
class SecurityLogController extends AbstractSecurityController
{
private $finderProvider;
private $authorization;
private $tokenStorage;

public function __construct(FinderProvider $finderProvider)
{
public function __construct(
FinderProvider $finderProvider,
AuthorizationCheckerInterface $authorization,
TokenStorageInterface $tokenStorage
) {
$this->finderProvider = $finderProvider;
$this->authorization = $authorization;
$this->tokenStorage = $tokenStorage;
}

/**
Expand All @@ -34,4 +44,26 @@ public function listAction(Request $request): JsonResponse
[]
));
}

/**
* @Route("/list/current", name="apiv2_logs_security_list_current", methods={"GET"})
*/
public function userLogSecurtiyAction(Request $request): JsonResponse
{
if (!$this->authorization->isGranted('IS_AUTHENTICATED_FULLY')) {
throw new AccessDeniedException();
}

$user = $this->tokenStorage->getToken()->getUser();

$query['hiddenFilters'] = [
'user' => $user->getUuid(),
];

return new JsonResponse($this->finderProvider->search(
SecurityLog::class,
$query,
[]
));
}
}
13 changes: 13 additions & 0 deletions src/main/core/Listener/AuthenticationFailureHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
namespace Claroline\CoreBundle\Listener;

use Claroline\AppBundle\Event\StrictDispatcher;
use Claroline\AppBundle\Persistence\ObjectManager;
use Claroline\CoreBundle\Entity\User;
use Claroline\CoreBundle\Event\CatalogEvents\SecurityEvents;
use Claroline\CoreBundle\Event\Security\AuthenticationFailureEvent;
use Symfony\Component\HttpFoundation\JsonResponse;
Expand All @@ -23,12 +25,19 @@ class AuthenticationFailureHandler extends DefaultAuthenticationFailureHandler
{
/** @var StrictDispatcher */
private $dispatcher;
/** @var ObjectManager */
private $objectManager;

public function setDispatcher(StrictDispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}

public function setObjectManager(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}

/**
* {@inheritdoc}
*/
Expand All @@ -47,6 +56,10 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio

private function dispatchAuthenticationFailureEvent(string $username, string $message): void
{
if ($user = $this->objectManager->getRepository(User::class)->findByName($username)) {
$username = $user[0];
}

$this->dispatcher->dispatch(
SecurityEvents::AUTHENTICATION_FAILURE,
AuthenticationFailureEvent::class,
Expand Down
3 changes: 3 additions & 0 deletions src/main/core/Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ services:
- method: setDispatcher
arguments:
- '@Claroline\AppBundle\Event\StrictDispatcher'
- method: setObjectManager
arguments:
- '@Claroline\AppBundle\Persistence\ObjectManager'

# array injection with annotations is not supported
claroline.plugin.validator:
Expand Down
2 changes: 2 additions & 0 deletions src/main/core/Resources/config/services/controller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ services:
public: true
arguments:
- '@Claroline\AppBundle\API\FinderProvider'
- '@security.authorization_checker'
- '@security.token_storage'

Claroline\CoreBundle\Controller\APINew\Log\MessageLogController:
parent: Claroline\AppBundle\Controller\AbstractSecurityController
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react'
import {PropTypes as T} from 'prop-types'

import {trans} from '#/main/app/intl/translation'
import {ListData} from '#/main/app/content/list/containers/data'

const SecurityLogList = (props) =>
<ListData
name={props.name}
fetch={{
url: props.url,
autoload: true
}}
definition={[
{
name: 'doer',
type: 'user',
label: trans('user'),
displayed: true
}, {
name: 'date',
label: trans('date'),
type: 'date',
options: {time: true},
displayed: true
}, {
name: 'details',
type: 'string',
label: trans('details'),
displayed: true
}, {
name: 'target',
type: 'user',
label: trans('target'),
displayed: false
}, {
name: 'event',
type: 'translation',
label: trans('event'),
displayed: false,
options: {
domain: 'security'
}
}
]}
/>

SecurityLogList.propTypes = {
name: T.string.isRequired,
url: T.oneOfType([T.string, T.array]),
definition: T.array
}

SecurityLogList.defaultProps = {
url: ['apiv2_logs_security_list_current'],
definition: []
}

export {
SecurityLogList
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react'
import {PropTypes as T} from 'prop-types'

import {trans} from '#/main/app/intl/translation'
import {LINK_BUTTON} from '#/main/app/buttons'
import {showBreadcrumb} from '#/main/app/layout/utils'

import {UserPage} from '#/main/core/user/components/page'
import {User as UserTypes} from '#/main/core/user/prop-types'
import {selectors} from '#/main/core/account/security/store/selectors'
import {SecurityLogList} from '#/main/core/account/security/components/list'

const SecurityMain = (props) =>
<UserPage
showBreadcrumb={showBreadcrumb()}
breadcrumb={[
{
type: LINK_BUTTON,
label: trans('my_account'),
target: '/account'
}, {
type: LINK_BUTTON,
label: trans('security'),
target: '/account/security'
}
]}
title={trans('security')}
user={props.currentUser}
>
<div style={{
marginTop: 60 // TODO : manage spacing correctly
}}>
<SecurityLogList
name={selectors.STORE_NAME}
url={['apiv2_logs_security_list_current']}
/>
</div>

</UserPage>

SecurityMain.propTypes = {
currentUser: T.shape(
UserTypes.propTypes
).isRequired
}

export {
SecurityMain
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {connect} from 'react-redux'

import {withReducer} from '#/main/app/store/components/withReducer'
import {selectors as securitySelectors} from '#/main/app/security/store'

import {reducer, selectors} from '#/main/core/account/security/store'
import {SecurityMain as SecurityMainComponent} from '#/main/core/account/security/components/main'

const SecurityMain = withReducer(selectors.STORE_NAME, reducer)(
connect(
(state) => ({
currentUser: securitySelectors.currentUser(state)
})
)(SecurityMainComponent)
)

export {
SecurityMain
}
10 changes: 10 additions & 0 deletions src/main/core/Resources/modules/account/security/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {trans} from '#/main/app/intl/translation'

import {SecurityMain} from '#/main/core/account/security/containers/main'

export default {
name: 'security',
icon: 'fa fa-fw fa-shield',
label: trans('security'),
component: SecurityMain
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {reducer} from '#/main/core/account/security/store/reducer'
import {selectors} from '#/main/core/account/security/store/selectors'

export {
reducer,
selectors
}
11 changes: 11 additions & 0 deletions src/main/core/Resources/modules/account/security/store/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {makeListReducer} from '#/main/app/content/list/store'

import {selectors} from '#/main/core/account/security/store/selectors'

const reducer = makeListReducer(selectors.STORE_NAME, {
sortBy: {property: 'date', direction: -1}
})

export {
reducer
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

const STORE_NAME = 'accountSecurity'

export const selectors = {
STORE_NAME
}
3 changes: 2 additions & 1 deletion src/main/core/Resources/modules/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ registry.add('ClarolineCoreBundle', {
*/
account: {
'profile': () => { return import(/* webpackChunkName: "core-account-profile" */ '#/main/core/account/profile') },
'privacy': () => { return import(/* webpackChunkName: "core-account-privacy" */ '#/main/core/account/privacy') }
'privacy': () => { return import(/* webpackChunkName: "core-account-privacy" */ '#/main/core/account/privacy') },
'security': () => { return import(/* webpackChunkName: "core-account-privacy" */ '#/main/core/account/security') }
},

/**
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/Subscriber/SecurityEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function logEvent(Event $event, string $eventName): void
$logEntry->setDetails($event->getMessage($this->translator));
$logEntry->setEvent($eventName);
$logEntry->setTarget($event->getUser());
$logEntry->setDoer($this->security->getUser());
$logEntry->setDoer($this->security->getUser() ?? $event->getUser());

$this->em->persist($logEntry);
$this->em->flush();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const reducer = combineReducers({
count: makeReducer({}, {
[makeInstanceAction(TOOL_LOAD, selectors.STORE_NAME)]: (state, action) => action.toolData.count
}),
securityLogs: makeListReducer(selectors.LIST_NAME),
securityLogs: makeListReducer(selectors.LIST_NAME, {
sortBy: {property: 'date', direction: -1}
}),
messageLogs: makeListReducer(selectors.MESSAGE_NAME)
})

Expand Down

0 comments on commit 093c47a

Please sign in to comment.