How to only allow approved users to log in to my wiki? - authentication

I have added a column in my wikidatabase in the user table called approved_account.
The standard value on that column is 0 (zero).
I would like to add an exception when a user tries to log in to the wiki, such that
if approved_account = 0 then the login attempt is denied.
Does anyone know how and where I should place that if statement?
Edit: I've come this far.
I am using the AbortLogin hook, since I need to verify if my statement is true every time a user tries to log in.
However, my code won't let anyone in. It blocks all login attempts, even if I have the correct value in the approved_account field.
Can anyone help me fix this?
<?php
/**
* Prevent a user from accessing this file directly and provide a helpful
* message explaining how to install this extension.
*/
if ( !defined( 'MEDIAWIKI' ) ) {
echo <<<EOT
To install the Test extension, put the following line in your LocalSettings.php file:
require_once( "$IP/extensions/approvedaccount.php" );
EOT;
exit( 1 );
}
// Extension credits that will show up on Special:Version
$wgExtensionCredits['parserhook'][] = array(
'name' => 'Approved Account extension',
'description' => 'Prevent login',
'author' => 'Me',
'url' => 'http://www.mediawiki.org/wiki/Extension:approvedaccount'
);
$wgHooks['AbortLogin'][] = 'approvedaccount::onAbortLogin';
class approvedaccount
{
public static function onAbortLogin( $user, $password, &$retval ) {
global $wgOut, $wgUser;
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
'user', // $table
array( 'user_name', 'approved_account' ), // $vars (columns of the table)
'user_name = "'.$wgUser.'"', // $conds
__METHOD__, // $fname = 'Database::select',
array( 'ORDER BY' => 'user_name ASC' ) // $options = array()
);
$output = '';
foreach( $res as $row ) {
$output .= 'Användarnamn: ' . $row->user_name . ' , Approved Account: ' . $row->approved_account . ".";
}
if ($row->approved_account = "1"){
//$this->loadDefaults();
// return false;
header("Location: http://hbg-whirlpool.emea.stream.corp/index.php?title=Special:UserLogout&returnto=Main+Page");
exit(); // you need to exit after a Location header is sent
}
}
}

You could do this with a simple AuthPlugin, overriding the strictUserAuth() method to return true for users that match the condition.
However, I suspect you're approaching this problem the wrong way. Why not just define a new user group, say, approved, and then add the corresponding record to the user_groups table for approved users? You won't be able to prevent unapproved users from logging in, but you can prevent them from making edits by only granting the edit permission to the approved group, like this:
$wgGroupPermissions['*']['edit'] = false;
$wgGroupPermissions['user']['edit'] = false;
$wgGroupPermissions['approved']['edit'] = true;
(If you wanted, you could even revoke the read permission from unapproved users too, but please read the warnings about restricting read access in MediaWiki first.)
Edit: I see a couple of problems with your AbortLogin hook.
Doing a 301 redirect and an exit() in the middle of the hook is probably not a very good idea. Sure, it probably will abort the login, but that's not really how the hook is meant to be used. Rather, you should just have the hook function return false to indicate that the login should be aborted or true to proceed with the normal login checks.
In any case, you're doing the exit() when the approved_account column is 1, which is presumably exactly when you don't want to abort the login.
...or, rather, you're doing the exit() always, because you used the assignment operator = instead of the comparison operator == in the condition, causing it to be always true. (Don't worry, that's a common bug in PHP and other C-like languages. One way to avoid is to get in the habit of using "Yoda conditionals" like 1 == $row->approved_account, which will produce an error if you leave out one =, since you can't assign to 1.)
Also, concatenating a User object with a string probably won't produce anything meaningful; and, even if it did, there would be an SQL injection vulnerability there. And besides, the hook parameters already include a User object, so you should use that instead of the global $wgUser (which might be stale during login anyway).
I admit that some of this stuff is really poorly documented. Besides the AbortLogin docs, I'd suggest looking at the general MediaWiki hook documentation, as well as the actual way the hook is called from SpecialUserlogin.php. For the database access, I'd also point you to the database wrapper function docs; unfortunately, the method documentation pages are giving 404 errors right now, so you'd again need to look directly in the source for the documentation.
Anyway, I'd rewrite your hook like this:
public static function onAbortLogin( $user, $password, &$retval, &$msg ) {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
'user',
'approved_account',
array( 'user_id' => $user->getID() ),
__METHOD__
);
if ( !$row || !$row->approved_account ) {
$retval = LoginForm::ABORTED; // actually the default, but let's be sure
$msg = 'login-abort-not-approved'; // optional: custom error message
return false;
}
else {
// account is approved, return true to proceed with other login checks
return true;
}
}
If you want the custom message, you'll also need to create the page MediaWiki:login-abort-not-approved on your wiki. (If you wanted to turn this into a proper MediaWiki extension, you could provide a default message in an i18n file, but that's probably overkill here.)
Edit 2: Yes, you can add as many hooks as you want in an extension. (In fact, you don't even need an extension, it's perfectly fine to define simple site-specific hooks directly in LocalSettings.php if you want.) I think something like this could work for an AddNewAccount hook to log the user out, although I must note that I haven't actually tested this:
public static function onAddNewAccount( $user, $byEmail ) {
global $wgUser;
// try to log out the new user only if they're actually logged in
if ( $user->getName() == $wgUser->getName() ) $user->logout();
return true;
}
The if clause is there because the AddNewAccount is also called when a user creates a new account while logged in to a pre-existing account, in which case logging them out from their original account would be an unwelcome surprise. (Technically, just if ( $user == $wgUser ) ought to suffice, but explicitly comparing the usernames rather than the object references seems safer.)
Note that logging the new user out at that point kind of yanks the carpet out from under the new user creation code, so some unusual things may happen. For example, I suspect that the user creation log may actually end up saying something like "NewUserName created the new user account NewUserName", and the "Account successfully created" page may temporarily show the user as logged in, even though they're actually not.
It would be much cleaner to somehow avoid the auto-login behavior in the first place, but I don't see any obvious way to do that without patching SpecialUserlogin.php: the only check that determines whether the new user is automatically logged in is if ( $this->getUser()->isAnon() ), which only checks whether a user is already logged in. Even faking that somehow (which would be an ugly kluge in itself) doesn't really seem practical, as far as I can tell.
If you don't mind patching the MediaWiki core, though, just replacing that condition with if ( false ) (or if ( false && $this->getUser()->isAnon() ), if you want to keep it self-documenting) should do the trick. Note that you could still keep the AddNewAccount hook as a backup, in case you forget to reapply the patch after upgrading or something.

Related

Firebase simple blog (confused with security rules)

I'm trying to create a simple todo or blog system based on React + ReactFire.
And after a hour of reading firebase tutorial confused about configuring firebase security rules.
Code for saving element :
this.props.itemsStore.push({
text : this.state.text,
done : false,
user : this.props.user.uid
})
Everything ok, but how i can get all records what owns only but authorized user?
This rules doesn't works :
"rules": {
"items" : {
".write" : "auth !== null",
"$item" : {
".read": "data.child('user').val() == auth.uid"
}
}
}
Seems to there no way to get all records only for one user, with security rules, instead of this, i should use something like filter. But again, i don't know how to filter elements in ReactFire, and in manuals no information.
As example how does it work in Parse http://i.stack.imgur.com/l9iXM.png
The Firebase security model has two common pitfalls:
permissions cascade: once you've granted a read or write permission on a specific level, you cannot take this permission away at a lower level
rules are not filters: (this is essentially a consequence of the previous pitfall) you cannot use security rules to return a different subset of children for specific users. Either a user has access to a node, or they don't have access to it.
You seem to be falling for that second pitfall. While the user can access each specific message that they are the user for, they cannot query the higher-level items node since they don't have read access to it.
If you want to secure a list of messages/todos for a specific user, you will need to store that data for that specific user.
items_per_user
$uid
$itemid: true
This is quite common in NoSQL database and is often called denormalizing. See this article called "denormalization is normal" on the Firebase web site. It's a bit outdated as far as the Firebase API goes, but the architectural principles on denormalizing still apply.
To then show the items for a user, you'd do:
ref.child('items_per_user')
.child(ref.getAuth().uid)
.on('child_added', function(snapshot) {
ref.child('items')
.child(itemId.key())
.once('value', function(itemSnapshot) {
console.log(itemSnapshot.val());
});
})
Many developer new to Firebase think that the inner loop will be too slow to load their data. But Firebase is very efficient when it comes to handling multiple requests, since it only opens a connection once per client and pipelines all the requests in the inner loop.
Keep in mind, Rules are not filters. They allow access to nodes based on criteria.
Here's an example simple structure where users 0 and 1 have stored text data within their node.
Data Structure
ToDo
a_user_id_0
text: "some text"
done: yes
a_user_id_1
text: "another text"
done: no
Rules
In this example rule, users can only read/write from nodes that belong to them within the ToDo node, so the path $user_id would be equal to their auth.id. It assumes the users has authenticated as well.
"ToDo": {
"$user_id": {
".read": "auth != null && $user_id == auth.uid",
".write": "auth != null && $user_id == auth.uid"
}
}
If user_0 was auth'd and attempted to read/write data from a_user_id_1 node, it would fail.

Ember Data Save method, Create vs update

I can't figure out how Ember determines if it should update or create a record. I would assume its based on the ID or on the Store entry, but it seems to be something else. The code example clarifies:
// this returns the user without making an api call
currentUser.get('store').find('user_detail', '49')
// this returns 49
currentUser.get('id')
// this returns true
currentUser.get('store').hasRecordForId('user_detail', 49)
// this issues a create to api/userDetails instead
// of updating /api/userDetails/49
currentUser.save()
// maybe this is a lead, not the 48 at the end
currentUser.toString()
// <EmberApp.UserDetail:ember461:48>
// it looks as though currentState is involved here
// http://emberjs.com/api/data/classes/DS.RootState.html
currentUser.currentState
// returns root.loaded.created.uncommitted
currentUser.get('currentState.stateName');
// also isNew is wrong and returns true
currentUser.get('isNew');
Let me explain why I have this issue. My app has a current user. If you logout I update the current user. So I set Ember.currentUser.setProperties(newUserData). I update the currentUser object so that ember automatically triggers updates throughout my app. If I would replace the currentUser Ember.currentUser = newUser; Nothing would update. If I cant solve the above problem an alternative solution for the swapping of the user object would also work.
This is how I handle the global user state
container.register('user:current', Ember.currentUser);
// and handle updates via Ember.currentUser.setProperties()
application.inject('controller', 'user', 'user:current');
application.inject('route', 'user', 'user:current');
A proper solution would replace Ember.currentUser, however doing that doesnt trigger updates.
A new model will have the isNew and isDirty properties set to true, an existing record that needs to be updated will only have isDirty set to true.
I'd recommend pushing your user one level deeper and not storing it on the Ember namespace, that way you can set it from anywhere else, yet still inject it during injection
var users = Em.Object.create({
current: currentUser
});
container.register('users:current', users, {instantiate: false});
// and handle updates via Ember.currentUser.setProperties()
application.inject('controller', 'users', 'users:current');
application.inject('route', 'users', 'users:current');
Then from any controller you can access/watch it on users.current, yet you can also set it using this.users.set('current', newUser) which would effect anyone watching that property on any controller or route.
Example: http://emberjs.jsbin.com/OxIDiVU/1145/edit
Additionally a lot of things you are doing are async calls and should use the promise pattern for viewing properties etc.

Authentication in liferay pages

We are having a portlet on a liferay page. We want to put up up a permission on every action method that is performed. For example on page A we have landed an XYZ portlet. Now we want that whenever there is any action performed form this portlet, we want to check that if the user is having a role to perform this action or not.
It wont be a good approach to put up the code in Action method of the portlet cause we are having approximately 20 such pages and portlets.
Can we have some sort of filter or so, so that each action request is checked if the user is having the access to the content or not.
Thank you...
My idea.
Use a filter to intercept all request
You can add a filter to the Liferay Servlet to check every request.
For that you can use a hook-plugin.
Look at this :
http://www.liferay.com/fr/documentation/liferay-portal/6.1/development/-/ai/other-hooks
http://connect-sam.com/2012/06/creating-servlet-filter-hook-in-liferay-6-1-to-restrict-access-based-on-ip-location/
Issue with filter is that you can't access ThemeDisplay or use PortalUtil.getUser(request).
So you must use work around like that :
private User _getUser(HttpServletRequest request) throws Exception {
HttpSession session = request.getSession();
User user = PortalUtil.getUser(request);
if (user != null) {
return user;
}
String userIdString = (String) session.getAttribute("j_username");
String password = (String) session.getAttribute("j_password");
if ((userIdString != null) && (password != null)) {
long userId = GetterUtil.getLong(userIdString);
user = UserLocalServiceUtil.getUser(userId);
}
return user;
}
Filtering the request
To filter the request you must get :
page id (Layout id in Liferay)
portlet id
portlet lifecycle
One more time using a filter is a pain because you can get the ThemeDisplay. These params are easy to get (with real object instancee) with ThemeDisplay.
So you must get this as parameter in the request.
final String portletId = ParamUtil.get((HttpServletRequest) servletRequest, "p_p_id", "");
final String layoutId = ParamUtil.get((HttpServletRequest) servletRequest, "plid", "");
final String portletLifecycle = ParamUtil.get((HttpServletRequest) servletRequest, "p_p_lifecycle", "");
Lifecycle details :
portletLifecycle is a int and the meaning of value is :
0 : RENDER
1 : ACTION (the one that interests you)
2 : RESOURCE
I think that with this data you can be able to define if user can or cannot make the action.
You can get user roles from the user.
You can get the current page and portlet linked to the request.
And you can know if the request is an action request.
Good luck with Liferay.
You can add freely configurable permissions to Liferay, see the Developer Guide for detailed information. My first guess on this would be that these affect "model resources", e.g. the data that your portlet is dealing with, rather than portlet-resources, e.g. permissions on the individual portlet itself. Think of portlet-permissions as permissions that are defined by Liferay, model-resources as permissions where you can come up with your own vocabulary on the actions, e.g. "UPDATE_ADDRESS" etc.
These permissions will typically be tied to roles, which are granted to users/usergroups/etc.
Based on this variability, it depends on the nature of your permissions if you can write a filter to generically check permissions, or if it depends on more than the individual action call.
If you determine that there is a generic solution, look up PortletFilters, they behave just like ServletFilters. These can easily provide a home for permission checks.
It's quite hard to cover this topic in such a short answer, I hope to have given enough resources for you to continue your quest.
You can abuse some existing portlet permission like "Add to Page" and set it to roles that should call the action.
And by the rendering and action phases validate "has the user necessary permission".
Or you can create new permission and configure it by portlet-configuration. This way is cleaner, but difficulty.

Edit Custom Attribute after Log In

I can only Edit a custom field when I before edit it by hand with the user administrator.
What's wrong with my code and what I should do to solve this??
Exactly, I'm trying to assign a value to a User custom attribute when It logs in the portal. And I'm not able to get ExpandoColumn in the conditions specified.
The problem is that ExpandoValue is null.
public class LoginAction extends Action {
public void run(HttpServletRequest req, HttpServletResponse res) {
User currentUser;
try {
currentUser = PortalUtil.getUser(req);
long userId = PrincipalThreadLocal.getUserId();
long companyId = currentUser.getCompanyId();
long groupId = GroupLocalServiceUtil.getGroup(companyId, "Guest").getGroupId();
/* Get de CustomField Segmentation */
ExpandoTable expandoTable = ExpandoTableLocalServiceUtil.getDefaultTable(companyId, User.class.getName());
ExpandoColumn expandoColumn = ExpandoColumnLocalServiceUtil.getColumn(companyId, User.class.getName(), expandoTable.getName(), "Segmentation");
if (expandoColumn != null) {
ExpandoValue expandoValue = ExpandoValueLocalServiceUtil.getValue(expandoTable.getTableId(), expandoColumn.getColumnId(), userId);
if (expandoValue != null) {
expandoValue.setData(finalsegment);
ExpandoValueLocalServiceUtil.updateExpandoValue(expandoValue);
}
}
}
Summary: My problem is that
ExpandoValue expandoValue = ExpandoValueLocalServiceUtil.getValue(expandoTable.getTableId(), expandoColumn.getColumnId(), classPK);
is Null when I access the Value of Custom Attribute. If I edit by hand this customattribute and then execute the same Code it works!!! I don't know why and I dont know how to solve this.
Edit (after your update to the question)
Take a look at the ExpandoValueLocalService javadoc: You'll find that there's a createExpandoValue method. Now guess the relationship between the scenario "You have not manually set the value at all, and you're getting back null as ExpandoValue" vs. "You have set it once and get back a value that you can update...
Another option would also be to just specify a default value for your expando value - this way you'll definitely have a value in there and you can unconditionally update it (at least until it's deliberately deleted - for robustness you should still cater for this possibility)
Original answer:
Where else but in the if condition do you go? Have you tried an else condition or do you get any exception before? E.g. you might need to create the 'default' table before you can just get it.
See this code for an example on how to access Expando Tables/Columns.
I didn't run your code, but of course exceptions might occur earlier as well. Or you might have made a mistake in configuring your LoginAction, so that it doesn't run at all.
By default, regular User role has no permissions to access Expando values.
Anyway, it is better to modify expando values with
User user = UserLocalService.getUserById(userId);
user.getExpandoBridge().setAttribute("attributeName", "attributeValue");
If you want to modify value with any permissions, use
user.getExpandoBridge().setAttribute("attributeName", "attributeValue", false);
Here I found the answer of the question..
You can manage the solution with the code proposed.
http://www.liferay.com/es/community/forums/-/message_boards/message/26154530

Laravel 4 authentication not working

I have a Laravel 4 app in which I have set up one user. In my login route I'm calling Auth::attempt with the email and password but it always comes back as false. I definitely have the password correct and the correct hash in the database as Hash::check returns true.
I think it may be due to using email as the login field instead of username, but I can't see any setting for that. This question implied you could add an option to config/auth.php but it didn't work. This question says to use username as the array key, but then I get SQL error because it tries to select on a username field in the database.
Do I need to add something to the User model to specify the username field? Here is my login route:
Route::post('login', function() {
// data from login form
$credentials = array(
'email' => Input::get('email'),
'password' => Input::get('password')
);
$auth = Hash::check(Input::get('password'), Hash::make('mypass'));
var_dump($auth); // this is TRUE
// login was good
$auth = Auth::attempt($credentials);
var_dump($auth); // this is FALSE
});
I found the problem. As suggested by Jason in the comment above, I had modified the models/User.php file and removed some functions that I didn't realise were necessary.
The getAuthIdentifier() and getAuthPassword() methods must be included in the User model for authentication!
In app/config/app.php make sure you have the 'key' set. This made me pull my hair out. Everything will apear to work, password seems hashed in the DB, but it will always return false until you set this key and re-hash your password into the DB.
"php artisan key:generate"
Had the same problem and made me sweat for hours. Definitively check your User.php model and make sure you have not overwritten the default one. Thanks Jason!