I want to extend Ion Auth to only allow certain email addresses to register.
I'm fairly sure I could hack this together and get something working, but as a newbie to codeigniter and ion auth I wish to find out if there is a "proper way" to be doing what I need?
For instance can I "extend" ion auth (so I can update ion auth core files without writing over my changes?).
I noticed there are also hooks including this one (in the register function):
$this->ci->ion_auth_model->trigger_events('pre_account_creation');
Where do these resolve and can I use this one in order to intercept registrations from email addresses which don't match a list of those I wish to register?
If so, how would I do it? I would need access to the $email variable from the register() function.
Or is it just a case of altering the base code from ion auth and not updating it in the future?
Thanks for any help you can give me. Don't worry about the email bit, I'm capable of working out whether an email address matches the required email domains, I'm more interested in what is the best way to go about extending the library.
Tom
EDIT: Hi Ben, thanks for your answer, and thanks for taking the time to have a look at my issue. Unfortunately this hasn't helped.
I guess what you're trying to do there is add a little bit to the sql query a "where in" clause? I guess that the where in bit is incorrect as there isn't a column name.
Also, at this point I can't modify the sql query satisfactorily to produce the required output. e.g. I can add a hook to a function which is literally $this->db->where('1=1') and this outputs this sql in the next query:
SELECT COUNT(*) AS `numrows` FROM (`users`) WHERE `1=1` AND `email` = 'rawr#rawr.com'
The AND email = 'rawr#rawr.com' bit will always still return no rows. It should be OR email = 'rawr#rawr.com', but without editing the Ion Auth core code then I won't be able to change this.
I am starting to suspect (from the last couple of hours of tinkering) that I may have to edit the ion auth core in order to achieve this.
Check out this example: https://gist.github.com/2881995
In the end I just wrote a little form_verification callback function which I put in the auth controller of ion_auth which checked through a list of allowed domains. :)
When you validate your form in the auth controller you add a callback:
$this->form_validation->set_rules('email', 'Email Address', required|callback_validate_email');
You create a method in the controller called validate_email:
function validate_email() {
if (strpos($this->input->post('email'), '#mycompany.com') === false) {
$this->form_validation->set_message('validate_email', 'Not official company email address.');
return false;
} else return true;
}
This will cause the creation of the user to fail, since all rules must pass. You also provide an error message. Just make sure to have this line on the form view side:
echo validation_errors();
Related
I'm trying to find all users w/ a specific permissions list in Sentry with laravel. The problem is that Sentry::findAllUsersWithAccess() returns an array().
as stated in their github repository i pinpointed their code to be
public function findAllWithAccess($permissions)
{
return array_filter($this->findAll(), function($user) use ($permissions)
{
return $user->hasAccess($permissions);
});
}
right now, it gets all users and filter it out with users with permission list. the big problem would be when I as a developer would get the set of users, it'll show ALL users, i'm developing an app which may hold thousands of users and i only need to get users with sepcific permission lists.
With regards to that would love to use one with a ->paginate() capability.
Any thoughts how to get it without getting all the users.
Why dont you override the findAllWithAccess() method and write your own implementation, which uses mysql where instead of array_filter().
I dont know your project structure and the underlying db schema, so all i can give you atm is the link to the eloquent documentation Querying Relations (whereHas).
In case you dont know where to start: its always a good idea to look at the ServiceProvider (SentryServiceProvider, where the UserProvider, which holds the findAllWidthAccess() method, is registered). Override the registerUserProvider method and return your own implementation of the UserProvider (with the edited findAllWithAccess() method).
Hope that will point you in the right direction.
In Laravel you can do pagination manually on arrays:
$paginator = Paginator::make($items, $totalItems, $perPage);
Check the docs: http://laravel.com/docs/pagination
i have create module using module builder , now i am having a field called as book Name
now if i give same book name 2 time t is accepting .
i don't want to use and plug in for checking duplicate value because i want to learn the customization through code .
so i can call ajax and check in data base weather the same book name is exist in db or not but i don't know how controller works in sugar crm . and how to call ajax in sugar crm .
can any one guide me , your help is much appreciated .
If you really want to accomplish this using ajax then I'd recommend an entryPoint as the way to go. This customization will require a couple of simple things. First you'll write a little bit of javascript to perform the actual ajax call. That ajax call will post to the entryPoint you write. The entryPoint will run the query for you and return a response to you in the edit view. So lets get started by writing the entryPoint first.
First, open the file custom/include/MVC/Controller/entry_point_registry.php. If the folder structure and file do not exist yet, go ahead and create them.
Add the following code to the entry_point_registry.php file:
$entry_point_registry['test'] = array('file' => 'custom/test.php', 'auth' => true);
Some quick explanation about that line:
The index value of test can be changed to whatever you like. Perhaps 'unique_book_value' makes more sense in your case. You'll see how this value is used in a minute.
The file value in the array points to where you're gonna put your actual code. You should also give this a more meaningful name. It does NOT need to match the array key mentioned above.
The 'auth' => true part determines whether or not the browser needs to have an active logged in session with SugarCRM or not. In this case (and almost all) I'd suggest keeping this to true.
Now lets look at the code that will go in custom/test.php (or in your case unique_book_name.php):
/* disclaimer: we are not gonna get all crazy with using PDO and parameterized queries at this point,
but be aware that there is potential for sql injection here. The auth => true will help
mitigate that somewhat, but you're never supposed to trust any input, blah blah blah. */
global $db; // load the global sugarcrm database object for your query
$book_name = urldecode($_REQUEST['book_name']); // we are gonna start with $_REQUEST to make this easier to test, but consider changing to $_POST when confirmed working as expected
$book_id = urldecode($_REQUEST['book_id']); // need to make sure this still works as expected when editing an existing record
// the $db->quote is an alias for mysql_real_escape_string() It still does not protect you completely from sql injection, but is better than not using it...
$sql = "SELECT id FROM book_module_table_name WHERE deleted = 0 AND name = '".$db->quote($book_name)."' AND id <> '".$db->quote($book_id)."'";
$res = $db->query($sql);
if ($db->getRowCount($res) > 0) {
echo 'exists';
}
else {
echo 'unique';
}
A note about using direct database queries: There are api methods you can use to accomplish this. (hint: $bean->retrieve_by_string_fields() - check out this article if you wanna go that route: http://developer.sugarcrm.com/2012/03/23/howto-using-the-bean-instead-of-sql-all-the-time/) However, I find the api to be rather slow and ajax should be as fast as possible. If a client asked me to provide this functionality there's a 99% chance I'd use a direct db query. Might use PDO and parameterized query if I'm feeling fancy that day, but it's your call.
Using the above code you should be able to navigate to https://crm.yourdomain.com/index.php?entryPoint=test and run the code we just wrote.
However at this point all you're gonna get is a white screen. If you modify the url to include the entryPoint part and it loads your home page or does NOT go to a white screen there are 3 potential causes:
You put something different for $entry_point_registry['test']. If so change the url to read index.php?entryPoint=whatever_you_put_as_the_array_key
You have sugar in a folder or something on your domain so instead of crm.yourdomain.com it is located somewhere ugly and stupid like yourdomain.com/sugarcrm/ if this is the case just make sure that your are modifying the url such that the actual domain portion is preserved. Okay I'll spell it out for you... https://yourdomain.com/sugarcrm/index.php?entryPoint=test
This is more rare, but for some reason that I cannot figure out apache sometimes needs to be reloaded when adding a new entrypoint. If you have shell access a quick /etc/init.d/apache2 reload should do the trick. If you don't have shell access you may need to open a ticket with your hosting provider (or get a fricking vps where you have some control!!!, c'mon man!)
Still not working? Did you notice the "s" in https? Try http instead and buy a fricking $9 ssl cert, geez man!
Okay moving on. Let's test out the entryPoint a bit. Add a record to the book module. Let's add the book "War of Art" (no, not Art of War, although you should give that a read too).
Now in the url add this: index.php?entryPoint=test&book_name=Art%20of%20War
Oh gawd that url encoding is hideous right! Don't worry about it.
You should hopefully get an ugly white screen with the text "exists". If you do let's make sure it also works the other way. Add a 2 to the book name in the url and hopefully it will now say "unique".
Quick note: if you're using Sugar you're probably also using mysql which is case insensitive when searching on strings. If you really need case sensitivity check out this SO article:
How can I make SQL case sensitive string comparison on MySQL?
Okay so now we have our entryPoint working and we can move on to the fun part of making everything all ajaxical. There are a couple ways to go about this, but rather than going the most basic route I'm gonna show you what I've found to be the most reliable route.
You probably will need to create the following file: custom/modules/CUSTOM_BOOK_MODULE/views/view.edit.php (I hope by now I don't need to point out changing that path to use your module name...
Assuming this file did not exist and we are starting from scratch here is what it will need to look like:
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class CUSTOM_BOOK_MODULEViewEdit extends ViewEdit
{
public function display()
{
// make sure it works in the subpanel too
$this->useForSubpanel = true;
// make the name value available in the tpl file
$this->ss->assign('name_value', $this->bean->name);
// load the parsed contents of the tpl into this var
$name_input_code = $this->ss->fetch('custom/modules/CUSTOM_BOOK_MODULE/tpls/unique_book_checker.tpl.js');
// pass the parsed contents down into the editviewdefs
$this->ss->assign('custom_name_code', $name_input_code);
// definitely need to call the parent method
parent::display();
}
}
Things are looking good. Now we gotta write the code in this file: custom/modules/CUSTOM_BOOK_MODULE/tpls/unique_book_checker.tpl.js
First a couple of assumptions:
We're going to expect that this is Sugar 6.5+ and jquery is already available. If you're on an earlier version you'll need to manually include jquery.
We're going to put the event listener on the name field. If the book name value that you want to check is actually a different field name then simply adjust that in the javascript below.
Here is the code for custom/modules/CUSTOM_BOOK_MODULE/unique_book_checker.tpl.js:
<input type="text" name="name" id="name" maxlength="255" value="{$name_value}" />
<span id="book_unique_result"></span>
{literal}
<script type="text/javascript">
$(document).ready(function() {
$('#name').blur(function(){
$('#book_unique_result').html('<strong> checking name...</strong>');
$.post('index.php?entryPoint=test', {book_name: $('#name').val(), book_id: $('[name="record"]').val()}, function(data){
if (data == 'exists') {
removeFromValidate('EditView', 'name');
addToValidate('EditView', 'name', 'float', true, 'Book Name Must be Unique.');
$('#book_unique_result').html('<strong style="color:red;"> ✗</strong>');
}
else if (data == 'unique') {
removeFromValidate('EditView', 'name');
addToValidate('EditView', 'name', '', true, 'Name Required');
$('#book_unique_result').html('<strong style="color:green;"> ✓</strong>');
}
else {
// uh oh! maybe you have php display errors on?
}
});
});
});
</script>
{/literal}
Another Note: When the code detects that the name already exists we get a little hacky and use Sugar's built in validation stuff to prevent the record from saving. Basically, we are saying that if the name already exists then the name value MUST be a float. I figured this is pretty unlikely and will do the trick. However if you have a book named 3.14 or something like that and you try to create a duplicate this code will NOT prevent the save. It will tell you that a duplicate was found, but it will not prevent the save.
Phew! Okay last two steps and they are easy.
First, open the file: custom/modules/CUSTOM_BOOK_MODULE/metadata/editviewdefs.php.
Next, find the section that provides the metadata for the name field and add this customCode attribute so that it looks like this:
array (
'name' => 'name',
'customCode' => '{$custom_name_code}',
),
Finally, you'll need to do a quick repair and rebuild for the metadata changes to take effect. Go to Admin > Repair > Quick Repair & Rebuild.
Boom! You should be good to go!
I am making an app that allow to register to a newsletter and then manage it.
There is the normal validation if the field is empty, if is email type, unique ecc..
But how can i check if the email really exists?
If someone enter a fake mail, that mail is entered in the database, and this can mess up the database. And there are other annoyance.
Is possible to check if the mail really exists?
Otherwise it is possible to automatically delete an unregistered user after 10 days?
You can create a rule to check if the e-mail format is valid:
$validator = Validator::make(
Input::all(),
array('email' => 'required|email')
);
if ($validator->fails())
{
return "This is not a valid e-mail";
}
But this will only check for the format (name#domain.tld), because there is no way to know if an e-mail address really exists.
Well... this is not entirely true, there is a way: send an e-mail to that address and if you don't get an error message, the address exists, probably.
The best way to do what you need is, when a user creates an account in your system, send him/her a confirmation e-mail, with a link where he/she should click to validate that address. If the account is not validated for x days you delete it. This is a question that might help you on that: Laravel 4: how to make confirmation email?.
About your last question: create an Artisan Command that checks for unvalidated accounts and delete them. Take a look at the docs, it's easy, Laravel does almost all the job for you. And you can run this command once a day using cron or task manager:
php /var/www/your-site-dir/artisan check:accounts
EDIT:
This question will show you how to create a command: Creating and using Laravel 4 commands
And in the method fire() you should do something like:
public function fire()
{
$dt = Carbon\Carbon::now();
User::where('activated', false)
->where('created_at', '<', $dt->subDays(10))
->delete();
}
There are some APIs you can use to verify the validity and existence of emails, but I am not sure how good they are. The free ones usually limit you to something like 10 an hour which probably would not be nearly enough for you, depending on how busy your site is.
Check out http://verify-email.org/register/levels.html
If you are worried about the sheer amount of emails being entered, you could probably log $_SERVER['REMOTE_ADDR'], which would be the client's IP address in the DB on emails and check to make sure that is unique as well before you save additional records to the table.
As far as how to actually validate the existence and validity of the entered email yourself, I believe you'd have to use fsockopen and issue smtp commands to servers. I did find this php class which attempts to do what we are talking about http://www.webdigi.co.uk/blog/wp-content/uploads/2009/01/smtpvalidateclassphp.txt
I doubt it works, but should get you a nice starting point.
The Dynamics AX 2009 Best Practice add-in is throwing the following error on a display method override.
"TwC: Validate access to return value from the display/edit method."
Here is my display method.
display ABC_StyleName lookupModuleName(ABC_StyleSettings _ABC_StyleSettings)
{
;
return ABC_Styles::find(_ABC_StyleSettings.StyleID).StyleName;
}
I'm assuming it wants me to check a config or security key before returning a result. Any suggestions/examples on where to start?
Thanks
This is a reminder that you need to consider whether the user should have access to the data you are returning from the function. For table fields, the kernel normally does this for you based on the security groups the user is in and the security keys set on fields.
To check if a user has access to a field, use the hasFieldAccess function. To see how this is used, look at the table methods BankAccountStatement.openingBalance() or CustTable.openInvoiceBalanceMST(). There are other helper functions to check security keys such as hasMenuItemAccess, hasSecuritykeyAccess, and hasTableAccess.
In your case, add this code:
if(!hasFieldAccess(tablenum(ABC_Styles),fieldnum(ABC_Styles,StyleName)))
{
throw error("#SYS57330");
}
Even after you add that code, you will still get the Best Practice error. To tell the compiler you have addressed the issue, you need to add the following comment immediatly before the function declaration:
//BP Deviation Documented
Let's say I'm building a web application whose user pages can be found at http://example.com/NAME. What's the best way to make sure the username doesn't conflict with a reserved word (e.g. 'about', 'contact', etc.)? I can think of two ways:
Maintain a list somewhere in my code. This is great and all, but means I have another piece of code I have to edit if I decide to, say, change the "about" page to "aboutus".
Request the URI (e.g. http://example.com/someusername) and check if it exists (doesn't return a 404). This feels kind of like a hack, but on the other hand it does exactly what it's supposed to do. On the other hand, I can't reserve anything without making a page for it.
What would be the best way to go about this? Manual validation of usernames is not an option. Thanks!
EDIT: I forgot to mention, the username has to go at the root, like this:
http://example.com/USERNAME
Not like this:
http://example.com/users/USERNAME
Hence why I'm asking this question. This is for technical reasons, don't ask.
I would strongly suggest using a unique path like http://example.com/users/NAME instead. Otherwise, what are you going to do if you want to add a reserved word, but a user has already taken it as their user name? You'll end up with all kinds of potential migration problems down the track.
Alternatively, if you must have something that goes straight off http://example.com/, could you possibly prefix all user names? So that user jerryjvl would translate to link http://example.com/user_jerryjvl?
If there is really no other possible solution, then I'd say either check user names against whatever data source determines what the 'reserved words' are, or make a lookup file / table / structure somewhere that contains all the reserved words.
In the interest of completeness, if you can't change the routing. Another possibility is to have your user routes and your non-user routes have a programmatic distinction. For example, if you appended a '_' to the end of each of your user routes, then you can make sure that users are located at: http://example.com/NAME_ and the other route would never end in '_'
How about changing your routing scheme so that users are at example.com/users/NAME ?
I maintain the reserved words inside the code.
This is the PERL code that I use in the http://postbit.com/ website to check if the usernamename is a reserved word:
# Black list of logins and sub-domains reserved keywords
my #black_list = qw(
about access account accounts add address adm admin administration
adult advertising affiliate affiliates ajax analytics android anon
anonymous api app apps archive atom auth authentication
...
);
my $username_normalized = lc($username);
$username_normalized =~ s/\W//gs; # 'log-in' -> 'login'
for my $this_username (#black_list) {
if ($username_normalized eq $this_username) {
die("This username is already taken. Please choose other username.\n");
}
}
The complete list of reserved names (like 'css', 'images', 'js', 'admin', 'root', 'old', 'test', 'www', 'admin', 'login', 'devel'...) with more than 300 login usernames is posted here:
http://blog.postbit.com/reserved-username-list.html
You only know what are these 'reserved' words. So better maintain a list and validate against it.
Another method will be if you use a CMS, then all these keywods 'about', 'contact' etc. will be there in your database. Validate against it.
Right next to the text box something like: "Please use your personal nickname or you real name. Usernames with common words indicating affiliation with the site administration may be revoked".
How about just create dummy accounts first with all the reserve words? just list all the possible ones and create them.
if you use
www.example.com/user/name
then there will be no problem but it seems like you'd like the URL to be short.
Maintain a list somewhere in my code. This is great and all, but means I have another piece of code I have to edit if I decide to, say, change the "about" page to "aboutus".
Your menus should be stored in an array/list. This way you would have only 1 piece of code to edit, not 2. =]
Then, since all menus are in one array, you can match username with elements in the array.
for example
$menu = array('About', 'Contact', 'Home')
if( in_array($username, $menu) ) {
echo 'invalid username'
}
You could always look and see how stackoverflow.com works.