Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
anis
anis-server
Commits
ea834ebc
Commit
ea834ebc
authored
Nov 24, 2020
by
François Agneray
Browse files
Merge branch 'authorization' into 'develop'
Authorization See merge request
!54
parents
c47ecc89
3335003f
Pipeline
#3561
passed with stages
in 3 minutes and 16 seconds
Changes
12
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
app/routes.php
View file @
ea834ebc
...
...
@@ -47,7 +47,7 @@ $app->group('', function (RouteCollectorProxy $group) {
'/dataset/{name}/attribute/{id}/distinct'
,
App\Action\AttributeDistinctAction
::
class
);
})
->
add
(
new
App\Middleware\AdminMiddleware
());
})
->
add
(
new
App\Middleware\AdminMiddleware
(
$container
->
get
(
SETTINGS
)[
'token'
]
));
$app
->
get
(
'/search/{dname}'
,
App\Action\SearchAction
::
class
);
$app
->
get
(
'/download-file/{dname}/[{fpath:.*}]'
,
App\Action\DownloadFileAction
::
class
);
app/settings.php
View file @
ea834ebc
...
...
@@ -31,7 +31,9 @@ return [
'level'
=>
getenv
(
'LOGGER_LEVEL'
)
],
'token'
=>
[
'issuer'
=>
getenv
(
'TOKEN_ISSUER'
),
'public_key_file'
=>
getenv
(
'TOKEN_PUBLIC_KEY_FILE'
)
//'issuer' => getenv('TOKEN_ISSUER'),
'enabled'
=>
getenv
(
'TOKEN_ENABLED'
),
'public_key_file'
=>
getenv
(
'TOKEN_PUBLIC_KEY_FILE'
),
'admin_role'
=>
getenv
(
'TOKEN_ADMIN_ROLE'
)
]
];
docker-compose.yml
View file @
ea834ebc
...
...
@@ -17,8 +17,9 @@ services:
LOGGER_NAME
:
"
anis-metamodel"
LOGGER_PATH
:
"
php://stderr"
LOGGER_LEVEL
:
"
debug"
TOKEN_
ISSUER
:
http://localhost:8180/auth/realms/anis
TOKEN_
ENABLED
:
1
TOKEN_PUBLIC_KEY_FILE
:
/data/public_key
TOKEN_ADMIN_ROLE
:
anis_admin
ports
:
-
8080:80
volumes
:
...
...
doctrine-proxy/__CG__AppEntityDataset.php
View file @
ea834ebc
...
...
@@ -64,10 +64,10 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy
public
function
__sleep
()
{
if
(
$this
->
__isInitialized__
)
{
return
[
'__isInitialized__'
,
'name'
,
'tableRef'
,
'label'
,
'description'
,
'display'
,
'count'
,
'vo'
,
'dataPath'
,
'
selectableRow
'
,
'project'
,
'datasetFamily'
,
'attributes'
];
return
[
'__isInitialized__'
,
'name'
,
'tableRef'
,
'label'
,
'description'
,
'display'
,
'count'
,
'vo'
,
'dataPath'
,
'
config'
,
'public
'
,
'project'
,
'datasetFamily'
,
'attributes'
];
}
return
[
'__isInitialized__'
,
'name'
,
'tableRef'
,
'label'
,
'description'
,
'display'
,
'count'
,
'vo'
,
'dataPath'
,
'
selectableRow
'
,
'project'
,
'datasetFamily'
,
'attributes'
];
return
[
'__isInitialized__'
,
'name'
,
'tableRef'
,
'label'
,
'description'
,
'display'
,
'count'
,
'vo'
,
'dataPath'
,
'
config'
,
'public
'
,
'project'
,
'datasetFamily'
,
'attributes'
];
}
/**
...
...
@@ -345,23 +345,45 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy
/**
* {@inheritDoc}
*/
public
function
get
SelectableRow
()
public
function
get
Config
()
{
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'get
SelectableRow
'
,
[]);
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'get
Config
'
,
[]);
return
parent
::
get
SelectableRow
();
return
parent
::
get
Config
();
}
/**
* {@inheritDoc}
*/
public
function
set
SelectableRow
(
$selectableRow
)
public
function
set
Config
(
$config
)
{
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'set
SelectableRow'
,
[
$selectableRow
]);
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'set
Config'
,
[
$config
]);
return
parent
::
setSelectableRow
(
$selectableRow
);
return
parent
::
setConfig
(
$config
);
}
/**
* {@inheritDoc}
*/
public
function
getPublic
()
{
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'getPublic'
,
[]);
return
parent
::
getPublic
();
}
/**
* {@inheritDoc}
*/
public
function
setPublic
(
$public
)
{
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'setPublic'
,
[
$public
]);
return
parent
::
setPublic
(
$public
);
}
/**
...
...
doctrine-proxy/__CG__AppEntityGroup.php
View file @
ea834ebc
...
...
@@ -64,10 +64,10 @@ class Group extends \App\Entity\Group implements \Doctrine\ORM\Proxy\Proxy
public
function
__sleep
()
{
if
(
$this
->
__isInitialized__
)
{
return
[
'__isInitialized__'
,
'id'
,
'label'
,
'dataset
Privilege
s'
];
return
[
'__isInitialized__'
,
'id'
,
'label'
,
'datasets'
];
}
return
[
'__isInitialized__'
,
'id'
,
'label'
,
'dataset
Privilege
s'
];
return
[
'__isInitialized__'
,
'id'
,
'label'
,
'datasets'
];
}
/**
...
...
@@ -213,12 +213,23 @@ class Group extends \App\Entity\Group implements \Doctrine\ORM\Proxy\Proxy
/**
* {@inheritDoc}
*/
public
function
getDataset
Privilege
s
()
public
function
getDatasets
()
{
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'getDataset
Privilege
s'
,
[]);
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'getDatasets'
,
[]);
return
parent
::
getDatasetPrivileges
();
return
parent
::
getDatasets
();
}
/**
* {@inheritDoc}
*/
public
function
setDatasets
(
$datasets
)
{
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'setDatasets'
,
[
$datasets
]);
return
parent
::
setDatasets
(
$datasets
);
}
/**
...
...
doctrine-proxy/__CG__AppEntityInstance.php
View file @
ea834ebc
...
...
@@ -64,10 +64,10 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy
public
function
__sleep
()
{
if
(
$this
->
__isInitialized__
)
{
return
[
'__isInitialized__'
,
'name'
,
'label'
,
'clientUrl'
,
'datasetFamilies'
];
return
[
'__isInitialized__'
,
'name'
,
'label'
,
'clientUrl'
,
'config'
,
'datasetFamilies'
];
}
return
[
'__isInitialized__'
,
'name'
,
'label'
,
'clientUrl'
,
'datasetFamilies'
];
return
[
'__isInitialized__'
,
'name'
,
'label'
,
'clientUrl'
,
'config'
,
'datasetFamilies'
];
}
/**
...
...
@@ -232,6 +232,28 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy
return
parent
::
setClientUrl
(
$clientUrl
);
}
/**
* {@inheritDoc}
*/
public
function
getConfig
()
{
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'getConfig'
,
[]);
return
parent
::
getConfig
();
}
/**
* {@inheritDoc}
*/
public
function
setConfig
(
$config
)
{
$this
->
__initializer__
&&
$this
->
__initializer__
->
__invoke
(
$this
,
'setConfig'
,
[
$config
]);
return
parent
::
setConfig
(
$config
);
}
/**
* {@inheritDoc}
*/
...
...
src/Action/GroupListAction.php
View file @
ea834ebc
...
...
@@ -45,7 +45,7 @@ final class GroupListAction extends AbstractAction
$parsedBody
=
$request
->
getParsedBody
();
// To work this action needs group information
foreach
(
array
(
'label'
)
as
$a
)
{
foreach
(
array
(
'label'
,
'datasets'
)
as
$a
)
{
if
(
$this
->
isEmptyField
(
$a
,
$parsedBody
))
{
throw
new
HttpBadRequestException
(
$request
,
...
...
@@ -70,10 +70,9 @@ final class GroupListAction extends AbstractAction
*/
private
function
postGroup
(
array
$parsedBody
):
Group
{
$group
=
new
Group
(
$this
->
getDatasets
(
$parsedBody
[
'datasets'
])
);
$group
=
new
Group
();
$group
->
setLabel
(
$parsedBody
[
'label'
]);
$group
->
setDatasets
(
$this
->
getDatasets
(
$parsedBody
[
'datasets'
]));
$this
->
em
->
persist
(
$group
);
$this
->
em
->
flush
();
...
...
src/Entity/Group.php
View file @
ea834ebc
...
...
@@ -12,9 +12,6 @@ declare(strict_types=1);
namespace
App\Entity
;
use
Doctrine\Common\Collections\Collection
;
use
Doctrine\Common\Collections\ArrayCollection
;
/**
* @Entity
* @Table(name="anis_group")
...
...
@@ -49,11 +46,6 @@ class Group implements \JsonSerializable
*/
protected
$datasets
;
public
function
__construct
(
array
$datasets
)
{
$this
->
datasets
=
new
ArrayCollection
(
$datasets
);
}
public
function
getId
()
{
return
$this
->
id
;
...
...
src/Middleware/AdminMiddleware.php
View file @
ea834ebc
...
...
@@ -20,20 +20,50 @@ use Nyholm\Psr7\Response as NyholmResponse;
final
class
AdminMiddleware
implements
MiddlewareInterface
{
/**
* Contains settings to handle Json Web Token
*
* @var array
*/
private
$settings
;
/**
* Create the classe before call process to execute this middleware
*
* @param array $settings Settings about token
*/
public
function
__construct
(
array
$settings
)
{
$this
->
settings
=
$settings
;
}
public
function
process
(
Request
$request
,
RequestHandler
$handler
):
Response
{
if
(
$request
->
getMethod
()
===
OPTIONS
||
$request
->
getMethod
()
===
GET
)
{
if
(
$request
->
getMethod
()
===
OPTIONS
||
$request
->
getMethod
()
===
GET
||
$this
->
settings
[
'enabled'
]
===
0
)
{
return
$handler
->
handle
(
$request
);
}
$token
=
$request
->
getAttribute
(
'token'
);
if
(
!
$token
)
{
return
(
new
NyholmResponse
())
->
withStatus
(
401
);
return
$this
->
getResponse
(
'HTTP 401: This url need a valid token'
,
401
);
}
if
(
!
in_array
(
'anis_admin'
,
$token
->
getClaim
(
'realm_access'
)
->
roles
))
{
return
(
new
NyholmResponse
())
->
withStatus
(
403
);
if
(
!
in_array
(
$this
->
settings
[
'admin_role'
]
,
$token
->
getClaim
(
'realm_access'
)
->
roles
))
{
return
$this
->
getResponse
(
'HTTP 403: This url need a higher level of permission'
,
403
);
}
return
$handler
->
handle
(
$request
);
}
private
function
getResponse
(
string
$message
,
int
$code
)
{
$resonse
=
new
NyholmResponse
();
$resonse
->
getBody
()
->
write
(
json_encode
(
array
(
'message'
=>
$message
)));
return
$resonse
->
withStatus
(
$code
);
}
}
src/Middleware/AuthorizationMiddleware.php
View file @
ea834ebc
...
...
@@ -15,6 +15,7 @@ namespace App\Middleware;
use
Psr\Http\Message\ResponseInterface
as
Response
;
use
Psr\Http\Message\ServerRequestInterface
as
Request
;
use
Psr\Http\Server\RequestHandlerInterface
as
RequestHandler
;
use
Slim\Exception\HttpUnauthorizedException
;
use
Nyholm\Psr7\Response
as
NyholmResponse
;
use
Psr\Http\Server\MiddlewareInterface
;
use
Lcobucci\JWT\Parser
;
...
...
@@ -58,7 +59,11 @@ final class AuthorizationMiddleware implements MiddlewareInterface
*/
public
function
process
(
Request
$request
,
RequestHandler
$handler
):
Response
{
if
(
$request
->
getMethod
()
===
OPTIONS
||
!
$request
->
hasHeader
(
'Authorization'
))
{
if
(
$request
->
getMethod
()
===
OPTIONS
||
!
$request
->
hasHeader
(
'Authorization'
)
||
$this
->
settings
[
'enabled'
]
===
0
)
{
return
$handler
->
handle
(
$request
);
}
...
...
@@ -66,7 +71,9 @@ final class AuthorizationMiddleware implements MiddlewareInterface
$bearer
=
$request
->
getHeader
(
'Authorization'
);
$data
=
explode
(
' '
,
$bearer
[
0
]);
if
(
$data
[
0
]
!==
'Bearer'
)
{
return
(
new
NyholmResponse
())
->
withStatus
(
401
);
return
$this
->
getUnauthorizedResponse
(
'HTTP 401: Authorization must contain a string with the following format -> Bearer JWT'
);
}
// Parse the JWT Token
...
...
@@ -74,18 +81,38 @@ final class AuthorizationMiddleware implements MiddlewareInterface
// Validating token (verifying expiration date and issuer)
$data
=
new
ValidationData
();
// TODO: Ajouter une config pour vérifier ou non le issuer
// $data->setIssuer($this->settings['issuer']);
if
(
!
$token
->
validate
(
$data
))
{
return
(
new
NyholmResponse
())
->
withStatus
(
401
);
return
$this
->
getUnauthorizedResponse
(
'HTTP 401: Access Token is not valid or has expired'
);
}
// Test token signature with the public key
$publicKey
=
new
Key
(
'file://'
.
$this
->
settings
[
'public_key_file'
]);
if
(
!
$token
->
verify
(
new
Sha256
(),
$publicKey
))
{
return
(
new
NyholmResponse
())
->
withStatus
(
401
);
return
$this
->
getUnauthorizedResponse
(
'HTTP 401: Access Token signature is not valid'
);
}
return
$handler
->
handle
(
$request
->
withAttribute
(
'token'
,
$token
));
}
// private function getPublicKey(string $issuer, string $kid): string
// {
// $urlOpenIdConfiguration = $issuer . '/.well-known/openid-configuration';
// $openIdConfiguration = json_decode(file_get_contents($urlOpenIdConfiguration), true);
// $jwksUri = $openIdConfiguration['jwks_uri'];
// $jwks = json_decode(file_get_contents($jwksUri), true);
// foreach ($jwks['keys'] as $jwk) {
// if ($jwk['kid'] === $kid) {
// return $jwk['x5c'];
// }
// }
// }
private
function
getUnauthorizedResponse
(
string
$message
)
{
$resonse
=
new
NyholmResponse
();
$resonse
->
getBody
()
->
write
(
json_encode
(
array
(
'message'
=>
$message
)));
return
$resonse
->
withStatus
(
401
);
}
}
tests/Action/GroupActionTest.php
0 → 100644
View file @
ea834ebc
<?php
/*
* This file is part of Anis Server.
*
* (c) Laboratoire d'Astrophysique de Marseille / CNRS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare
(
strict_types
=
1
);
namespace
App\Tests\Action
;
use
PHPUnit\Framework\TestCase
;
use
Nyholm\Psr7\ServerRequest
;
use
Nyholm\Psr7\Response
;
use
Slim\Exception\HttpNotFoundException
;
use
Slim\Exception\HttpBadRequestException
;
use
App\tests\EntityManagerBuilder
;
use
App\Entity\Group
;
use
App\Entity\Database
;
use
App\Entity\Project
;
use
App\Entity\Instance
;
use
App\Entity\DatasetFamily
;
use
App\Entity\Dataset
;
final
class
GroupActionTest
extends
TestCase
{
private
$action
;
private
$entityManager
;
protected
function
setUp
():
void
{
$this
->
entityManager
=
EntityManagerBuilder
::
getInstance
();
$this
->
action
=
new
\
App\Action\GroupAction
(
$this
->
entityManager
);
}
public
function
testOptionsHttpMethod
():
void
{
$request
=
$this
->
getRequest
(
'OPTIONS'
);
$response
=
(
$this
->
action
)(
$request
,
new
Response
(),
array
());
$this
->
assertSame
(
$response
->
getHeaderLine
(
'Access-Control-Allow-Methods'
),
'GET, PUT, DELETE, OPTIONS'
);
}
public
function
testGroupIsNotFound
():
void
{
$this
->
expectException
(
HttpNotFoundException
::
class
);
$this
->
expectExceptionMessage
(
'Group with id 1 is not found'
);
$request
=
$this
->
getRequest
(
'GET'
);
$response
=
(
$this
->
action
)(
$request
,
new
Response
(),
array
(
'id'
=>
1
));
$this
->
assertEquals
(
404
,
(
int
)
$response
->
getStatusCode
());
}
public
function
testGetAGroupById
():
void
{
$group
=
$this
->
addAGroup
();
$request
=
$this
->
getRequest
(
'GET'
);
$response
=
(
$this
->
action
)(
$request
,
new
Response
(),
array
(
'id'
=>
1
));
$this
->
assertSame
(
json_encode
(
$group
),
(
string
)
$response
->
getBody
());
}
public
function
testEditAGroupEmptyLabelField
():
void
{
$this
->
addAGroup
();
$this
->
expectException
(
HttpBadRequestException
::
class
);
$this
->
expectExceptionMessage
(
'Param label needed to edit the group'
);
$request
=
$this
->
getRequest
(
'PUT'
)
->
withParsedBody
(
array
());
$response
=
(
$this
->
action
)(
$request
,
new
Response
(),
array
(
'id'
=>
1
));
$this
->
assertEquals
(
400
,
(
int
)
$response
->
getStatusCode
());
}
public
function
testEditAGroupWithoutDataset
():
void
{
$fields
=
array
(
'label'
=>
'New_label'
,
'datasets'
=>
array
()
);
$this
->
addAGroup
();
$request
=
$this
->
getRequest
(
'PUT'
)
->
withParsedBody
(
$fields
);
$response
=
(
$this
->
action
)(
$request
,
new
Response
(),
array
(
'id'
=>
1
));
$this
->
assertSame
(
json_encode
(
array_merge
([
'id'
=>
1
],
$fields
)),
(
string
)
$response
->
getBody
());
}
public
function
testEditAGroupWithDataset
():
void
{
$dataset
=
$this
->
addADataset
();
$fields
=
array
(
'label'
=>
'New_label'
,
'datasets'
=>
array
(
$dataset
->
getName
())
);
$this
->
addAGroup
();
$request
=
$this
->
getRequest
(
'PUT'
)
->
withParsedBody
(
$fields
);
$response
=
(
$this
->
action
)(
$request
,
new
Response
(),
array
(
'id'
=>
1
));
$this
->
assertSame
(
json_encode
(
array_merge
([
'id'
=>
1
],
$fields
)),
(
string
)
$response
->
getBody
());
}
public
function
testDeleteAGroup
():
void
{
$this
->
addAGroup
();
$request
=
$this
->
getRequest
(
'DELETE'
);
$response
=
(
$this
->
action
)(
$request
,
new
Response
(),
array
(
'id'
=>
1
));
$this
->
assertSame
(
json_encode
(
array
(
'message'
=>
'Group with id 1 is removed!'
)),
(
string
)
$response
->
getBody
()
);
}
protected
function
tearDown
():
void
{
$this
->
entityManager
->
getConnection
()
->
close
();
}
private
function
getRequest
(
string
$method
):
ServerRequest
{
return
new
ServerRequest
(
$method
,
'/group/1'
,
array
(
'Content-Type'
=>
'application/json'
));
}
private
function
addAGroup
():
Group
{
$group
=
new
Group
();
$group
->
setLabel
(
'Group1'
);
$group
->
setDatasets
(
array
());
$this
->
entityManager
->
persist
(
$group
);
$this
->
entityManager
->
flush
();
return
$group
;
}
private
function
addProject
():
Project
{
$database
=
new
Database
();
$database
->
setLabel
(
'Test1'
);
$database
->
setDbName
(
'test1'
);
$database
->
setType
(
'pgsql'
);
$database
->
setHost
(
'db'
);
$database
->
setPort
(
5432
);
$database
->
setLogin
(
'test'
);
$database
->
setPassword
(
'test'
);
$this
->
entityManager
->
persist
(
$database
);
$project
=
new
Project
(
'anis_project'
);
$project
->
setLabel
(
'Test project'
);
$project
->
setDescription
(
'Test description'
);
$project
->
setLink
(
'http://test.com'
);
$project
->
setManager
(
'User1'
);
$project
->
setDatabase
(
$database
);
$this
->
entityManager
->
persist
(
$project
);
$this
->
entityManager
->
flush
();
return
$project
;
}
private
function
addInstance
():
Instance
{
$instance
=
new
Instance
(
'aspic'
,
'Aspic'
);
$instance
->
setClientUrl
(
'http://cesam.lam.fr/aspic'
);
$this
->
entityManager
->
persist
(
$instance
);
$this
->
entityManager
->
flush
();
return
$instance
;
}
private
function
addDatasetFamily
():
DatasetFamily
{
$instance
=
$this
->
addInstance
();
$family
=
new
DatasetFamily
(
$instance
);
$family
->
setLabel
(
'Default dataset'
);
$family
->
setDisplay
(
10
);
$this
->
entityManager
->
persist
(
$family
);
$this
->
entityManager
->
flush
();
return
$family
;
}
private
function
addADataset
():
Dataset
{
$project
=
$this
->
addProject
();
$family
=
$this
->
addDatasetFamily
();
$dataset
=
new
Dataset
(
'obs_cat'
);
$dataset
->
setTableRef
(
'v_obs_cat'
);
$dataset
->
setLabel
(
'Obscat label'
);
$dataset
->
setDescription
(
'Obscat description'
);
$dataset
->
setDisplay
(
10
);
$dataset
->
setCount
(
10000
);
$dataset
->
setVo
(
false
);
$dataset
->
setDataPath
(
'/mnt/obs_cat'
);
$dataset
->
setPublic
(
true
);
$dataset
->
setProject
(
$project
);
$dataset
->
setDatasetFamily
(
$family
);
$this
->
entityManager
->
persist
(
$dataset
);
$this
->
entityManager
->
flush
();
return
$dataset
;
}
}
tests/Action/GroupListActionTest.php
0 → 100644
View file @
ea834ebc
<?php
/*
* This file is part of Anis Server.
*
* (c) Laboratoire d'Astrophysique de Marseille / CNRS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare
(
strict_types
=
1
);
namespace
App\Tests\Action
;
use
PHPUnit\Framework\TestCase
;
use
Nyholm\Psr7\ServerRequest
;
use
Nyholm\Psr7\Response
;
use
Slim\Exception\HttpBadRequestException
;
use
App\tests\EntityManagerBuilder
;
use
App\Entity\Group
;
use
App\Entity\Database
;
use
App\Entity\Project
;
use
App\Entity\Instance
;
use
App\Entity\DatasetFamily
;
use
App\Entity\Dataset
;
final
class
GroupListActionTest
extends
TestCase
{
private
$action
;
private
$entityManager
;
protected
function
setUp
():
void
{
$this
->
entityManager
=
EntityManagerBuilder
::
getInstance
();
$this
->
action
=
new
\
App\Action\GroupListAction
(
$this
->
entityManager
);
}
public
function
testOptionsHttpMethod
():
void
{
$request
=
$this
->
getRequest
(
'OPTIONS'
);
$response
=
(
$this
->
action
)(
$request
,
new
Response
(),
array
());
$this
->
assertSame
(
$response
->
getHeaderLine
(
'Access-Control-Allow-Methods'
),
'GET, POST, OPTIONS'
);
}
public
function
testGetAllGroups
():
void
{