Following Grails 3.0.11 Interceptors document, I code my own Interceptors as below:
class AuthInterceptor {
int order = HIGHEST_PRECEDENCE;
AuthInterceptor() {
println("AuthInterceptor.AuthInterceptor(): Enter..............");
// ApiController.index() and HomeController.index() don't need authentication.
// Other controllers need to check authentication
matchAll().excludes {
match(controller:'api', action:'index);
match(controller:'home', action:'index');
}
}
boolean before() {
println "AuthInterceptor.before():Enter----------------->>>>>>";
log.debug("AuthInterceptor.before(): params:${params}");
log.debug("AuthInterceptor.before(): session.id:${session.id}");
log.debug("AuthInterceptor.before(): session.user:${session.user?.englishDisplayName}");
if (!session.user) {
log.debug("AuthInterceptor.before(): display warning msg");
render "Hi, I am gonna check authentication"
return false;
} else {
return true;
}
}
boolean after() {
log.debug("AuthInterceptor.after(): Enter ...........");
true
}
void afterView() {
// no-op
}
}
class P2mController {
def index() {
log.debug("p2m():Enter p2m()..............")
render "Hi, I am P2M";
}
}
When I test http://localhost:8080/p2m/index, from log console, I saw that P2mController.index() is executed without been checked authentication.
However, when I test http://localhost:8080/api/index or http://localhost:8080/home/index, AuthInterceptor.check() is executed and the browser displays
Hi, I am gonna check authentication
I wish P2mController been checked authentication, and HomeController.index() and ApiController.index() don't need to be checked authentication. But from the log and response, the result is opposite.
Where is wrong in my AuthInterceptor ?
You want to do this instead:
matchAll().excludes(controller:'api', action:'index')
.excludes(controller:'home', action:'index')
And don't forget the single-quote after the first 'index'.
Related
After a good amount of time, I found the solution through an authentication plugin that is easy to install via composer with the command:
composer require "cakephp/authentication:^2.0"
After that, just generate the crud via terminal and check them to send json type data. It can be done as follows:
$this->response
->withType('application/json')
->withStatus(200)
->withStringBody(json_encode($dataOrMessage));
Add the function in entity to encrypt the password that the user registers:
protected function _setPassword(string $password) : ?string {
if (strlen($password) > 0) {
return (new DefaultPasswordHasher())->hash($password);
}
}
Implementing the AuthenticationServiceProviderInterface interface in the Application class and adding its requirements, in addition to the login and logout functions of the aforementioned plugin, you can view here
The login function will look something like this:
public function login() {
if ($this->request->is('post')) {
$result = $this->Authentication->getResult();
if ($result && $result->isValid()) {
return $this->response
->withType('application/json')
->withStatus(200)
->withStringBody(json_encode($result->getData()));
} else {
return $this->response
->withType('application/json')
->withStatus(401)
->withStringBody(json_encode(['message' => 'User or password incorrect!']));
}
}
}
There is not only this solution, but it was the one I found and that I found to be easier to implement.
I'm new to Laravel and I'm writing a user management System on my own.
At this time,
I can CRUD permissions, roles and users,
I can check the permissions by the AuthServiceProvider#boot method like this:
public function boot()
{
Gate::before( function (User $user , $permission) {
// App administrator
if($user->getPermissions()->contains('appAll'))
{
return true;
}
// Permission check
return $user->getPermissions()->contains($permission);
});
}
In my AdminUserController, I can check the permissions like that:
public function index()
{
if( Gate::check('createUser') || Gate::check('readUser') || Gate::check('updateUser') || Gate::check('deleteUser')) {
return view('userMgmt/users/index', [
'users' => User::getUsersWithRolesWithTexts()
]);
}
else
{
return redirect(route('home'))->withErrors('You do not have required permission');
}
}
That is working well.
BUT
Is this the right way to wrap each controller method with:
if( Gate::check(...) ...) {
//Do what the method is supposed to do
}
else
{
return redirect(route('SOME.ROUTE'))->withErrors('SOME ERROR OCCURRED');
}
It would be nice if someone can give me some ideas.
Tank you
There is a controller helper function named authorize that you can call from any method in a controller that extends App\Http\Controllers\Controller. This method accepts the action name and the model, and it will throw an exception if the user is not authorized. So instead of the if...else statement, it will be one line:
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// The current user can update the blog post...
}
I implemented google sign-in in my application like so:
fun Application.module(testing: Boolean = false) {
install(CallLogging)
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
val jwtIssuer = environment.config.property("jwt.domain").getString()
val jwtAudience = environment.config.property("jwt.audience").getString()
val jwtRealm = environment.config.property("jwt.realm").getString()
val jwkProvider = JwkProviderBuilder(URL("https://www.googleapis.com/oauth2/v3/certs"))
.cached(10, 24, TimeUnit.HOURS)
.rateLimited(10, 1, TimeUnit.MINUTES)
.build()
install(Authentication) {
jwt {
verifier(jwkProvider) {
withIssuer(jwtIssuer)
withAudience(jwtAudience)
}
realm = jwtRealm
validate { credentials ->
if (credentials.payload.audience.contains(jwtAudience))
JWTPrincipal(credentials.payload)
else
null
}
}
}
routing {
authenticate {
post("/token-sign-in") {
val payload = call.principal<JWTPrincipal>()?.payload ?: error("JWTPrincipal not found")
call.respond(
UserWire(
id = payload.subject,
email = payload.getClaim("email").asString(),
name = payload.getClaim("name").asString(),
profilePictureUrl = payload.getClaim("picture").asString()
)
)
}
}
}
}
I want to authenticate the user every single time they access one of the routes, but I want to have both google and firebase-auth login as an option. The thing is that they require different methods to check the authenticity of the given token, hence I need two authentication methods.
I was thinking of including an "AuthenticationProvider: "Google|Firebase"" in the header of the call, and according to its value, I would decide which authentication method should be called.
So something like this:
fun Application.module(testing: Boolean = false) {
install(Authentication) {
jwt("google") {
// verify google sign in token
}
jwt("firebase") {
// verify firebase token
}
firebaseOrGoogle("firebaseOrGoogle") {
// check header value for auth provider
// verify token with either "firebase" or "google" auth methods
}
}
routing {
authenticate("firebaseOrGoogle") {
post("/token-sign-in") {
// ...
}
get("/transactions") {
// ...
}
}
}
}
Is this at all possible?
If this is possible please could you provide some code as to how to dynamically decide which authentication method should be called?
As an alternative solution, you can configure an authentication feature to try proving the identity of a user by both methods. The first successful check wins. To do that just pass those two configuration names to the authenticate method:
routing {
authenticate("google", "firebase") {
post("/token-sign-in") {
// ...
}
get("/transactions") {
// ...
}
}
}
The order of arguments determines what check comes first.
I am transitioning from Selenium to WebdriverIO and I'm running into some difficulty regarding function reusability. Let me demonstrate with an example:
<nav>
<div><a>Clients</a></div>
<div><a>Accounts</a></div>
<div><a>Packages</a></div>
</nav>
lets say I have a navigation bar with 3 links above. When I land on this page, I want to check if each link exists. My function may look something like this:
class LoginPage extends Page {
get clientsLink() { return $('//a[contains(., "Clients")]'); }
isTabDisplayed() {
if (this.clientsLink.isDisplayed()) {
return true;
} else {
false
}
}
}
this is fine except I would have to write 2 more getters for Accounts and Packages and so my class would look like this:
class LoginPage extends Page {
get clientsLink() { return $('//a[contains(., "Clients")]'); }
get accountsLink() { return $('//a[contains(., "Accounts")]'); }
get packagesLink() { return $('//a[contains(., "Packages")]'); }
isClientTabDisplayed(tab) {
if (this.clientsLink.isDisplayed()) {
return true;
} else {
false
}
}
isAccountsTabDisplayed(tab) {
if (this.accountsLink.isDisplayed()) {
return true;
} else {
false
}
}
isPackagesTabDisplayed(tab) {
if (this.packagesLink.isDisplayed()) {
return true;
} else {
false
}
}
}
at this point, my anxiety kicks in and I start to think of ways I can reuse the isTabDisplayed function where I can pass a string to the getter with my tab name, or something along the lines of that.
Unfortunately, getters do not accept parameters and so far I have not found any resources on google that can help me to solve this issue (most common being Page Object Model which doesn't seem to address this problem)
Is my thought process out of line that I am striving for reusable code in UI testing or am I not googling for correct patterns?
Page Objects in WebdriverIO are just plain ES6 classes. Have a look through the documentation on ES6 classes to understand how you can create functions that you can pass arguments in to.
Now, that being said, what you're doing here isn't necessary. Instead of creating a function which references a getter, why not just reference that getter directly in your test?
const login = new LoginPage();
const isAccountsTabDisplayed = login.accountsLink.isDisplayed();
There's really no reason to create a wrapper function around this.
I want to achieve the following:
In my installation there are two bundles, ApiBundle and BackendBundle. Users are defined in BackendBundle, though I could put them in a UserBundle later.
ApiBundle basically provides a controller with api methods like for example getSomething().
BackendBundle has the user entities, services and some views like a login form and a backend view. From the backend controller I would want to access certain api methods.
Other api methods will be requested from outside. Api methods will be requested through curl.
I would want to have different users for both purposes. The User class implements UserInterface and has properties like $username, $password and $apiKey.
Now basically I want to provide an authentication method through login form with username and password, and another authentication method for api calls through curl from outside, that only will require the apiKey.
In both cases, the authenticated user then should have access to different ressources.
My security.yml so far looks like this:
providers:
chain_provider:
chain:
providers: [db_username, db_apikey]
db_username:
entity:
class: BackendBundle:User
property: username
db_apikey:
entity:
class: BackendBundle:User
property: apiKey
encoders:
BackendBundle\Entity\User:
algorithm: bcrypt
cost: 12
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
form_login:
login_path: login
check_path: login
default_target_path: backend
csrf_token_generator: security.csrf.token_manager
logout:
path: /logout
target: /login
provider: chain_provider
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: ROLE_API }
- { path: ^/backend, roles: ROLE_BACKEND }
Question 1: How can I achieve that users from the same entity can authenticate differently and access certain ressources? The desired behaviour is authentication with username/password OR only apikey.
Question 2: How can I achieve, that api methods return a json if the requester is not authenticated properly, instead of returning the view for the login form? Eg. I want to return something like { 'error': 'No access' } instead of the html for the login form if someone requests /api/getSomething and of course I want to show the login form if someone requests /backend/someroute.
Every help is very much appreciated! :)
The symfony docs say:
The main job of a firewall is to configure how your users will authenticate. Will they use a login form? HTTP basic authentication? An API token? All of the above?
I think my question basically is, how can I have login form AND api token authentication at the same time.
So maybe, I need something like this: http://symfony.com/doc/current/security/guard_authentication.html#frequently-asked-questions
Question 1: When you want to authenticate users by apiKey only, then best possible solution would be implement own User provider. The solution is well decribed in the Symfony doc: http://symfony.com/doc/current/security/api_key_authentication.html
EDIT - You can have as many user providers as you want and if one fails, then another becomes to play - described here https://symfony.com/doc/current/security/multiple_user_providers.html
Down below is code for ApiKeyAuthenticator which gets the token and calls ApiKeyUserProvider to find/get user for it. In case user is found, than is provided to Symfony security. ApiKeyUserProvider needs UserRepository to user operations - I'm sure you have one, otherwise write it.
Code isn't tested, so little bit of tweaking may be necessary.
So lets get to work:
src/BackendBundle/Security/ApiKeyAuthenticator.php
namespace BackendBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
protected $httpUtils;
public function __construct(HttpUtils $httpUtils)
{
$this->httpUtils = $httpUtils;
}
public function createToken(Request $request, $providerKey)
{
//use this only if you want to limit apiKey authentication only for certain url
//$targetUrl = '/login/check';
//if (!$this->httpUtils->checkRequestPath($request, $targetUrl)) {
// return;
//}
// get an apikey from authentication request
$apiKey = $request->query->get('apikey');
// or if you want to use an "apikey" header, then do something like this:
// $apiKey = $request->headers->get('apikey');
if (!$apiKey) {
//You can return null just skip the authentication, so Symfony
// can fallback to another authentication method, if any.
return null;
//or you can return BadCredentialsException to fail the authentication
//throw new BadCredentialsException();
}
return new PreAuthenticatedToken(
'anon.',
$apiKey,
$providerKey
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
if (!$userProvider instanceof ApiKeyUserProvider) {
throw new \InvalidArgumentException(
sprintf(
'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
get_class($userProvider)
)
);
}
$apiKey = $token->getCredentials();
$username = $userProvider->getUsernameForApiKey($apiKey);
if (!$username) {
// CAUTION: this message will be returned to the client
// (so don't put any un-trusted messages / error strings here)
throw new CustomUserMessageAuthenticationException(
sprintf('API Key "%s" does not exist.', $apiKey)
);
}
$user = $userProvider->loadUserByUsername($username);
return new PreAuthenticatedToken(
$user,
$apiKey,
$providerKey,
$user->getRoles()
);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// this contains information about *why* authentication failed
// use it, or return your own message
return new JsonResponse(//$exception, 401);
}
}
src/BackendBundle/Security/ApiKeyUserProvider.php
namespace BackendBundle\Security;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use BackendBundle\Entity\User;
use BackendBundle\Entity\UserORMRepository;
class ApiKeyUserProvider implements UserProviderInterface
{
private $userRepository;
public function __construct(UserORMRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function getUsernameForApiKey($apiKey)
{
//use repository method for getting user from DB by API key
$user = $this->userRepository->...
if (!$user) {
throw new UsernameNotFoundException('User with provided apikey does not exist.');
}
return $username;
}
public function loadUserByUsername($username)
{
//use repository method for getting user from DB by username
$user = $this->userRepository->...
if (!$user) {
throw new UsernameNotFoundException(sprintf('User "%s" does not exist.', $username));
}
return $user;
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Expected an instance of ..., but got "%s".', get_class($user)));
}
if (!$this->supportsClass(get_class($user))) {
throw new UnsupportedUserException(sprintf('Expected an instance of %s, but got "%s".', $this->userRepository->getClassName(), get_class($user)));
}
//use repository method for getting user from DB by ID
if (null === $reloadedUser = $this->userRepository->findUserById($user->getId())) {
throw new UsernameNotFoundException(sprintf('User with ID "%s" could not be reloaded.', $user->getId()));
}
return $reloadedUser;
}
public function supportsClass($class)
{
$userClass = $this->userRepository->getClassName();
return ($userClass === $class || is_subclass_of($class, $userClass));
}
}
Services definition:
services:
api_key_user_provider:
class: BackendBundle\Security\ApiKeyUserProvider
apikey_authenticator:
class: BackendBundle\Security\ApiKeyAuthenticator
arguments: ["#security.http_utils"]
public: false
And finally security provider config:
providers:
chain_provider:
chain:
providers: [api_key_user_provider, db_username]
api_key_user_provider:
id: api_key_user_provider
db_username:
entity:
class: BackendBundle:User
property: username
I encourage you to study Symfony docs more, there is very good explanation for the authentication process, User entities, User providers, etc.
Question 2: You can achieve different response types for access denied event by defining own Access denied handler:
namespace BackendBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
class AccessDeniedHandler implements AccessDeniedHandlerInterface
{
public function handle(Request $request, AccessDeniedException $accessDeniedException)
{
$route = $request->get('_route');
if ($route == 'api')) {
return new JsonResponse($content, 403);
} elseif ($route == 'backend')) {
return new Response($content, 403);
} else {
return new Response(null, 403);
}
}
}