Hook into Drupal registration and validate user info against business logic - drupal-6

I want to hook into the registration module. I already have a database of 50000 users who use my old website. Now I am migrating to Drupal.
I still haven't migrated the entries to drupal database. I will be checking against my old database.
When a user tries to register in Drupal, I need to check whether the username he gave is already present in that list of 50000 (and growing) entries. If it exists, I need to cancel the registration showing an error msg saying username exists..
Which hook should I use? If my code figures that the validation failed, How can I tell drupal to display an error msg?
Edit: I hooked into the hook_user and checked for the 'validate' op. I am able to validate and assign error messages. But it is happening for all forms. I want to validate only the new account creation form. How can I do that?
Thanks.

You should register an additional validation callback function for the registration form using hook_form_FORM_ID_alter(), somewhat like so:
// Alter the registration form
function yourModuleName_form_user_register_alter(&$form, &$form_state) {
// Add your own function to the array of validation callbacks
$form['#validate'][] = 'yourModuleName_user_register_validate';
}
// Perform your own validation
function yourModuleName_user_register_validate($form, &$form_state) {
// Extract the submitted name
$name = $form_state['values']['name'];
// Check it according to your own logic
$is_valid_name = your_check_for_valid_name();
// File error, when not valid
if (!$is_valid) {
form_set_error('name', t('Name already taken, please choose a different one'));
}
}

Henrik Opel answer work on Drupal 6. For Drupal 7 use yourModuleName_form_user_register_form_alter

Here are some examples for Drupal 7:
/**
* Implements of hook_user_insert().
*/
function foo_user_insert(&$edit, $account, $category) {
// foo_user_submit($edit, $account);
}
/**
* Implementation of hook_user_delete().
*/
function foo_user_delete($account) {
// foo_user_delete($account);
}
/**
* Implements hook_form_FORM_ID_alter().
* Form ID: user_register_form
*/
function foo_form_user_register_form_alter($form, &$form_state) {
if ($form['#user_category'] == 'account' && !isset($form['#user']->uid)) {
// Foo code
}
}
/**
* Implements hook_form_FORM_ID_alter().
* Form ID: user_profile_form
*/
function foo_form_user_profile_form_alter($form, &$form_state) {
// Set a custom form validate and submit handlers.
$form['#validate'][] = 'foo_account_validate';
$form['#submit'][] = 'foo_account_submit';
}
/**
* Implements of hook_form_alter().
* This is the same as: hook_form_FORM_ID_alter()
*/
function foo_form_alter(&$form, $form_state, $form_id) {
switch ($form_id) {
case "user_profile_form":
case "user_register_form":
break;
}
}

Consider using the Username Originality AJAX check module: https://www.drupal.org/project/username_check

Related

BreezeJS SaveChanges() security issue

I'm using BreezeJS and have a question regarding how data is saved. Here's my code and comments
[Authorize]
/*
* I want to point out the security hole here. Any Authorized user is able to pass to this method
* a saveBundle which will be saved to the DB. This saveBundle can contain anything, for any user,
* or any table.
*
* This cannot be stopped at the client level as this method can be called from Postman, curl, or whatever.
*
* The only way I can see to subvert this attack would be to examine the saveBundle and verify
* no data is being impacted that is not owned or related directly to the calling user.
*
* Brute force could be applied here because SaveResult contains Errors and impacted Entities.
*
*/
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return _efContext.SaveChanges(saveBundle);
}
To limit access to a callers ability to retrieve data I first extract from the access_token the user_id and limit all my queries to include this in a where clause, making it somewhat impossible for a user to retrieve another users data.
But that would not stop a rogue user who had a valid access_token from calling SaveChanges() in a brute force loop with incremental object ids.
Am I way off on this one? Maybe I'm missing something.
Thanks for any help.
Mike
The JObject saveBundle that the client passes to the SaveChanges method is opaque and hard to use. The Breeze ContextProvider converts that to a map of entities and passes it to the BeforeSaveEntities method. BeforeSaveEntities is a method you would implement on your ContextProvider subclass, or in a delegate that you attach to the ContextProvider, e.g.:
var cp = new MyContextProvider();
cp.BeforeSaveEntitiesDelegate += MySaveValidator;
In your BeforeSaveEntities or delegate method, you would check to see if the entities can be saved by the current user. If you find an entity that shouldn't be saved, you can either remove it from the change set, or throw an error and abort the save:
protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(
Dictionary<Type, List<EntityInfo>> saveMap)
{
var user = GetCurrentUser();
var entityErrors = new List<EFEntityError>();
foreach (Type type in saveMap.Keys)
{
foreach (EntityInfo entityInfo in saveMap[type])
{
if (!UserCanSave(entityInfo, user))
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Forbidden)
{ ReasonPhrase = "Not authorized to make these changes" });
}
}
}
return saveMap;
}
You will need to determine whether the user should be allowed to save a particular entity. This could be based on the role of the user and/or some other attribute, e.g. users in the Sales role can only save Client records that belong to their own SalesRegion.

Netsuite: how can I add an employee with a role, access, and a password via SuiteScript?

This code work great for creating the employee, but the password and giveAccess fields are not set:
function CreateEmployee() {
nlapiLogExecution('DEBUG','running create employee',1);
var employeeRec = nlapiCreateRecord('employee');
employeeRec.setFieldValue('lastname', 'Aloe');
employeeRec.setFieldValue('firstname', 'Danny');
employeeRec.setFieldValue('email', 'test9475#test232.org');
employeeRec.setFieldValue('subsidiary', 3);
employeeRec.setFieldValue('giveAccess', true);
employeeRec.setFieldValue('role', 3);
employeeRec.setFieldValue('password', 'Somepassword1!');
employeeRec.setFieldValue('password2', 'Somepassword1!');
var id = nlapiSubmitRecord(employeeRec);
nlapiLogExecution('DEBUG','done: ' + id + ' employee',id);
var result = new Object();
result.id = id;
return result;
}
When I go to the web interface and pull up the employee record, the "Access" tab does not have the giveAccess checkbox checked. And trying to login as the new user does not work. Is there a trick other than employeeRec.setFieldValue to set these values?
Not sure if you ever found the answer or not... I know this is an old post.
I was able to update the user roles programmatically Below is the Mass Update Script I'm using. It can show you how to add a role to a user with SuiteScript.
function massUpdate(recType,recID){
var roleID=1234;
var empRec=nlapiLoadRecord(recType,recID);
var roleCount=empRec.getLineItemCount('roles');
var thiRole=empRec.setLineItemValue('roles','selectedrole',roleCount+1,roleID);
var submitRec=nlapiSubmitRecord(empRec);
}
The issue you had was that you were setting a field value, instead of a sublist field value. Hope that helps.
Checking the box by itself, whether through the UI or scripting, isn't enough to grant access. You also need to specify a role for the user to use.
First glance through the scriptable records browser, it doesn't seem that roles are scriptable except as search filters and search columns.
https://system.netsuite.com/help/helpcenter/en_US/RecordsBrowser/2013_2/Records/employee.html
Here is a working example in suitescrpit 2.0 to add a role on an employee.
/**
* #NApiVersion 2.x
* #NScriptType UserEventScript
* #NModuleScope SameAccount
*/
define(['N/record'],
/**
* #param {record} record
*/
function(record) {
function beforeSubmit(scriptContext) {
log.debug('in the script');
scriptContext.newRecord.setSublistValue({
sublistId: 'roles',//'jobresources
fieldId: 'selectedrole',//'jobresource',
line: 3,
value: 4
});
}
return {
beforeSubmit: beforeSubmit,
};
});

Yii file permission verify doesn't work

I have a question about yii.
I have some videos and the view action is performed based on some privileges. Some videos may be hidden, just for some user categories or for all. I can handle this from yii filter or something, but the real problem is what happen if someone knows the video file path, i can't handle the permissions in this case.
My solution is ok, but I have one problem - when someone access direct a file I redirect it (from htaccess) to a page, when I verify the user permission based on it's id. When I try to access the file from url everything is ok, the permission filter works fine, but when I the file is called from the player something it's wrong. From what I saw, the Yii::app()->user->id is empty.
Any recommendation?
This is my UserIdentity class:
class UserIdentity extends CUserIdentity
{
private $_id;
/**
* Authenticates a user.
* #return boolean whether authentication succeeds.
*/
public function authenticate()
{
$user=Utilizator::model()->findByAttributes(array('username'=>$this->username));
if($user===null)
{
$this->errorCode=self::ERROR_USERNAME_INVALID;
}
else
{
if($user->password!==$user->encrypt($this->password))
{
$this->errorCode=self::ERROR_PASSWORD_INVALID;
}
else
{
$this->_id = $user->id;
$this->errorCode=self::ERROR_NONE;
}
}
return !$this->errorCode;
}
public function getId()
{
return $this->username;
}
}

How to allow action using security component?

I am getting this error
Call to undefined method SecurityComponent::allowedActions()
When I try to allow singup action in controller like this
public function beforeFilter() {
parent::beforeFilter();
$this->Security->allowedActions(array('sign-up'));
$this->Auth->allow('login','signup','index','activate','logout','forgot','reset','display');
if($this->Auth->user('id')) {
$this->set('logged_in', true);
} else {
$this->set('logged_in', false);
}
}
public $components = array('RequestHandler');
if i remove
$this->Security->allowedActions(array('sign-up'));
when I submit signup form, It shows your request has ben blackholed
There is no such method, allowedActions is a property of the SecurityComponent.
http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#SecurityComponent::$allowedActions
$this->Security->allowedActions = array('sign-up');
Also you are using signup in AuthComponent::allow(), so make sure sign-up is really the correct name of the action (which I really doubt as this would be invalid PHP syntax).

When are user roles refreshed and how to force it?

First off, I'm not using FOSUserBundle and I can't because I'm porting a legacy system which has its own Model layer (no Doctrine/Mongo/whatsoever here) and other very custom behavior.
I'm trying to connect my legacy role system with Symfony's so I can use native symfony security in controllers and views.
My first attempt was to load and return all of the user's roles in the getRoles() method from the Symfony\Component\Security\Core\User\UserInterface. At first, it looked like that worked. But after taking a deeper look, I noticed that these roles are only refreshed when the user logs in. This means that if I grant or revoke roles from a user, he will have to log out and back in for the changes to take effect. However, if I revoke security roles from a user, I want that to be applied immediately, so that behavior isn't acceptable to me.
What I want Symfony to do is to reload a user's roles on every request to make sure they're up-to-date. I have implemented a custom user provider and its refreshUser(UserInterface $user) method is being called on every request but the roles somehow aren't being refreshed.
The code to load / refresh the user in my UserProvider looks something like this:
public function loadUserByUsername($username) {
$user = UserModel::loadByUsername($username); // Loads a fresh user object including roles!
if (!$user) {
throw new UsernameNotFoundException("User not found");
}
return $user;
}
(refreshUser looks similar)
Is there a way to make Symfony refresh user roles on each request?
So after a couple of days trying to find a viable solution and contributing to the Symfony2 user mailing list, I finally found it. The following has been derived from the discussion at https://groups.google.com/d/topic/symfony2/NDBb4JN3mNc/discussion
It turns out that there's an interface Symfony\Component\Security\Core\User\EquatableInterface that is not intended for comparing object identity but precisely to
test if two objects are equal in security and re-authentication context
Implement that interface in your user class (the one already implementing UserInterface). Implement the only required method isEqualTo(UserInterface $user) so that it returns false if the current user's roles differ from those of the passed user.
Note: The User object is serialized in the session. Because of the way serialization works, make sure to store the roles in a field of your user object, and do not retrieve them directly in the getRoles() Method, otherwise all of that won't work!
Here's an example of how the specific methods might look like:
protected $roles = null;
public function getRoles() {
if ($this->roles == null) {
$this->roles = ...; // Retrieve the fresh list of roles
// from wherever they are stored here
}
return $this->roles;
}
public function isEqualTo(UserInterface $user) {
if ($user instanceof YourUserClass) {
// Check that the roles are the same, in any order
$isEqual = count($this->getRoles()) == count($user->getRoles());
if ($isEqual) {
foreach($this->getRoles() as $role) {
$isEqual = $isEqual && in_array($role, $user->getRoles());
}
}
return $isEqual;
}
return false;
}
Also, note that when the roles actually change and you reload the page, the profiler toolbar might tell you that your user is not authenticated. Plus, looking into the profiler, you might find that the roles didn't actually get refreshed.
I found out that the role refreshing actually does work. It's just that if no authorization constraints are hit (no #Secure annotations, no required roles in the firewall etc.), the refreshing is not actually done and the user is kept in the "unauthenticated" state.
As soon as you hit a page that performs any kind of authorization check, the user roles are being refreshed and the profiler toolbar displays the user with a green dot and "Authenticated: yes" again.
That's an acceptable behavior for me - hope it was helpful :)
In your security.yml (or the alternatives):
security:
always_authenticate_before_granting: true
Easiest game of my life.
From a Controller, after adding roles to a user, and saving to the database, simply call:
// Force refresh of user roles
$token = $this->get('security.context')->getToken()->setAuthenticated(false);
Take a look here, set always_authenticate_before_granting to true at security.yml.
I achieve this behaviour by implementing my own EntityUserProvider and overriding loadByUsername($username) method :
/**
* Load an user from its username
* #param string $username
* #return UserInterface
*/
public function loadUserByUsername($username)
{
$user = $this->repository->findOneByEmailJoinedToCustomerAccount($username);
if (null === $user)
{
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
}
//Custom function to definassigned roles to an user
$roles = $this->loadRolesForUser($user);
//Set roles to the user entity
$user->setRoles($roles);
return $user;
}
The trick is to call setRoles each time you call loadByUsername ... Hope it helps
Solution is to hang a subscriber on a Doctrine postUpdate event. If updated entity is User, same user as logged, then I do authenticate using AuthenticationManager service. You have to inject service container (or related services) to subscriber, of course. I prefer to inject whole container to prevent a circular references issue.
public function postUpdate(LifecycleEventArgs $ev) {
$entity = $ev->getEntity();
if ($entity instanceof User) {
$sc = $this->container->get('security.context');
$user = $sc->getToken()->getUser();
if ($user === $entity) {
$token = $this->container->get('security.authentication.manager')->authenticate($sc->getToken());
if ($token instanceof TokenInterface) {
$sc->setToken($token);
}
}
}
}
Sorry i cant reply in comment so i replay to question. If someone new in symfony security try to get role refresh work in Custom Password Authentication then inside function authenticateToken :
if(count($token->getRoles()) > 0 ){
if ($token->getUser() == $user ){
$passwordValid=true;
}
}
And do not check for passwords from DB/LDAP or anywhere. If user come in system then in $token are just username and had no roles.
I've been battling this for Symfony4, and I think I've finally settled down to a solution.
The thing is that in my case, the roles depend on the "company" the user is working with. It may be a CEO in one company, but an operator in another one, and the menus, permissions, etc. depend on the company. When switching companies, the user must not re-login.
Finally I've done the following:
Set the firewall to stateless.
In the FormAuthentication class, I set an attribute in the session explicitely, with the username.
I set up another Guard, which essentially take this attribute and loads the user for it from the database, for every single request.
class FormAuthenticator extends AbstractFormLoginAuthenticator
{
/** Constructor omitted */
public function supports(Request $request)
{
return 'app_login' === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'nomusuari' => $request->request->get('nomusuari'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['nomusuari']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $userProvider->loadUserByUsername($credentials['nomusuari']);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Invalid user/password');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
$valid = $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
return $valid;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$request->getSession()->set("user_username",$token->getUsername());
return new RedirectResponse(
$this->urlGenerator->generate("main")
);
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}
The SessionAuthenticator (returns JSON, you may have to adapt it):
class SessionAuthenticator extends AbstractGuardAuthenticator
{
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request)
{
return $request->getSession()->has("user_username");
}
/**
* Called on every request. Return whatever credentials you want to
* be passed to getUser() as $credentials.
*/
public function getCredentials(Request $request)
{
return $request->getSession()->get("user_username","");
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
if (null === $credentials) {
// The token header was empty, authentication fails with HTTP Status
// Code 401 "Unauthorized"
return null;
}
// if a User is returned, checkCredentials() is called
/*return $this->em->getRepository(User::class)
->findOneBy(['apiToken' => $credentials])
;*/
return $userProvider->loadUserByUsername($credentials);
}
public function checkCredentials($credentials, UserInterface $user)
{
// Check credentials - e.g. make sure the password is valid.
// In case of an API token, no credential check is needed.
// Return `true` to cause authentication success
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = [
// you might translate this message
'message' => 'Authentication Required'
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
return false;
}
}
Finally, my security.yaml:
main:
anonymous:
stateless: true
guard:
entry_point: App\Security\FormAuthenticator
authenticators:
- App\Security\SessionAuthenticator
- App\Security\FormAuthenticator
Working fine. I can see the changes in the toolbar, and the Roles are refreshed.
HTH,
Esteve

Resources