I have setup a CRUD area on my frontendAPI.php file (testing my models)... and I even managed to secure it. I would like to do this the proper way... I would like to establish a separate directory/ Page for the Admins. Please advise on this.
Still new at this but I'm trying to do the same for a news page, think i've got the login part working but having problems with the CRUD (will post a question on it shortly) - i have a table to populate with data from an rss feed (but will be manually populated with a CRUD to start with) and then have a page on the front end to pull out the details using views to format each news story.
Create a new directory called /page/Admin
Create a new file here based on the function e.g. news.php containing
class page_admin_news extends Page {
function init(){
parent::init();
$p=$this;
$crud=$p->add('CRUD');
$g=$crud->setModel('News');
if($crud->grid)
$crud->grid->addPaginator(30);
}
}
In Frontend.php, you need to enable the login - for an admin only access, the BasicAuth may be sufficient but there are also classes to use a database to obtain username and password infromation e.g. for a membership site - heres the basic one.
// If you wish to restrict access to your pages, use BasicAuth class
$auth=$this->add('BasicAuth')
->allow('demo','demo')
;
You need to modify Frontend.php to enable pages that can be viewed
without being logged in
$auth->allowPage('index');
$auth->allowPage('news');
$auth->allowPage('links');
$auth->allowPage('About');
if (!$auth->isPageAllowed($this->api->page))
{
$auth->check();
}
And also in Frontend.php, you need to create a different menu if logged in. Note the login and logout pages dont actually exist.
if ($auth->isLoggedIn())
{
$this->add('Menu',null,'Menu')
->addMenuitem('News','admin_news')
->addMenuitem('logout')
;
} else {
$this->add('Menu',null,'Menu')
->addMenuitem('News','news')
->addMenuitem('Links','links')
->addMenuItem('About')
->addMenuItem('Login')
;
}
When you login, it goes to page/index.php by default so if you want it to redirect to a particular page when you log in so you can add this to page/index.php
class page_index extends Page {
function init(){
parent::init();
$p=$this;
if($this->api->auth->isLoggedIn())
$this->api->redirect('admin_news');
Hope that helps.
Trev
Related
I have a website where all the pages are accessible to the public except for one Releases page which is user specific or maybe to a specific group .I have a seperate login page to gain access to 'Releases' page based on authentication.How do I go about this?Using Acl or Authorize function?I am very confused..Also do i need to use the same users table for authenticating this page, in that case do I use this User login page as an elemnt in my other login page.Could somebody please hint me on how to proceed?
ACL is overkill for many situations.
What I normally do is something like this in my controller:
public function releases() {
$this->_allowedGroups(array(1,2,3));
// rest of code here
}
Then in my app controller:
public function _allowedGroups($groups=array()) {
if( !in_array($this->Auth->user('group_id'), $groups) ) {
$this->redirect(array('controller'=>'users', 'action'=>'login'));
}
}
Acl should do your work.
And is there any specific need that you are using a separate login page??
A single login page and and a single users table should suffice your needs if you implement acl. Only those users who have rights to view the Requests page will be allowed to do so.
you may do something like this..
on core.php, put
Configure::write('Routing.prefixes', array('release'));
and do the verification on the AppController:
class AppController extends Controller{
public function beforeFilter(){
if (isset($this->params['prefix']) and $this->params['prefix'] == 'release'){
if ($this->Session->read("User.type") != 'admin'){
//redirect the user or throw an error...
}
}
}
}
so, youdomain.com/release/* will only be accesible by your administrators...
also, i don't see why you need two logins pages... you could just put a flag on your users table saying if the user is or not an admin... and on the login, set the User.type property on session.
if you don't need of complex permissions control, i think you don't need use ACL.
I am building a REST API for a DotNetNuke 6 website, making use of DNN's MVC-based Services Framework. However, I don't have any background in authentication, so I'm not even sure where to start.
Basically, we want our clients to be able to make GET requests for their portal's data, and we want some clients (but not all) to be able to POST simple updates to their user data.
I've been trying to search for information, but the trouble is I'm not sure what I'm searching for. DNN has different logins and roles, but I'm not sure if or how they factor in. I've heard of things like oAuth but my understanding of it is at the most basic level. I don't know if it's what I need or not and if or how it applies to DNN. Can anyone point me in the right direction?
UPDATE:
Based on the answer below about tying it with a module and further research, here is what I have done:
I created a module just for this service, and I added two special permissions for it: "APIGET" and "APIPOST." I assigned these to some test roles/test accounts in DNN. I wrote a custom authorize attribute that, given the module ID, checks if the current user has the necessary permission (either through roles or directly). As far as I can tell, tab ID is irrelevant in my case.
It appears to be working both with a web browser (based on the DNN account I'm logged into) and with a php script that sends an HTTP request with an account username/password.
The authorize attribute:
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Security;
using DotNetNuke.Security.Permissions;
using System.Web;
public class MyAuthorize : DotNetNuke.Web.Services.AuthorizeAttributeBase
{
public const string AuthModuleFriendlyName = "MyAuthModule";
public const string GETPermission = "APIGET";
public const string POSTPermission = "APIPOST";
public string Permission { get; set; }
protected override bool AuthorizeCore(HttpContextBase context)
{
ModuleController mc = new ModuleController();
ModuleInfo mi = mc.GetModuleByDefinition(PortalController.GetCurrentPortalSettings().PortalId, AuthModuleFriendlyName);
ModulePermissionCollection permCollection = mi.ModulePermissions;
return ModulePermissionController.HasModulePermission(permCollection, Permission);
}
}
The controller:
("mytest" is the endpoint for both GET and POST)
public class MyController : DnnController
{
[ActionName("mytest")]
[AcceptVerbs(HttpVerbs.Get)]
[DnnAuthorize(AllowAnonymous = true)]
[MyAuthorize(Permission = MyAuthorize.GETPermission)]
public string myget(string id = "")
{
return "You have my permission to GET";
}
[ActionName("mytest")]
[AcceptVerbs(HttpVerbs.Post)]
[DnnAuthorize(AllowAnonymous = true)]
[MyAuthorize(Permission = MyAuthorize.POSTPermission)]
public string mypost(string id = "")
{
return "You have my permission to POST";
}
}
The main way that you tie a service in the DNN Services Framework into DNN permissions is to associate the permissions with a module instance. That is, you'll require users of your service to identify which module they're calling from/about (by sending ModuleId and TabId in the request [headers, query-string, cookies, form]), then you can indicate what permissions they need on that module to take a particular action on the service.
You can use the SupportedModules attribute on your service, and pass in a comma-delimited list of module names, to ensure that only your own modules are being allowed. Then, add the DnnModuleAuthorize attribute at the service or individual action level to say what permission the user needs on that module. In your instance, you can also add the AllowAnonymous attribute on the GET actions, and have one DnnModuleAuthorize on the service, for the POST methods (and anything else). Note that you cannot put the AllowAnonymous attribute on the controller; it will override authorizations put at the action, making it impossible to make actions more restrictive.
You'll also want to add the ValidateAntiForgeryToken attribute on the POST actions, to protect against CSRF attacks.
If you don't have a module that naturally associates its permissions with your service, you can create one just for that purpose, solely to expose itself as a permissions management utility.
Once you've figured out the authorization piece above, DNN will take care of authentication using your forms cookie (i.e. AJAX scenarios are taken care of automatically), or via basic or digest authentication (for non-AJAX scenarios). That said, if you're doing non-AJAX, you'll need to figure out a way to validate the anti-forgery token only when it applies.
The Services Framework in DNN is what you are after. It allows you to provide a REST API that plugs directly into DNN security.
Here are some articles to help you get started:
http://www.dotnetnuke.com/Resources/Wiki/Page/Services-Framework-WebAPI.aspx
http://www.dotnetnuke.com/Resources/Blogs/EntryId/3327/Getting-Started-with-DotNetNuke-Services-Framework.aspx
Note, there are some difference in DNN 6 and DNN 7 when using the Services Framework:
http://www.dotnetnuke.com/Resources/Blogs/EntryId/3514/Converting-Services-Framework-MVC-to-WebAPI.aspx
Just wanted to note that the DnnModuleAuthorize attribute takes a PermissionKey parameter for custom permissions so you can do stuff like this:
[DnnModuleAuthorize(PermissionKey = "DELETEDATA")]
[HttpPost]
public HttpResponseMessage DeleteData(FormDataCollection data)
It doesn't look like you can supply your own error message with this so you might to wrap your method body like this instead and leave off the custom permission attribute:
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
[HttpPost]
public HttpResponseMessage DeleteData(FormDataCollection data)
{
var errorMessage = "Could not delete data";
if (ModulePermissionController.HasModulePermission(ActiveModule.ModulePermissions,"DELETEDATA"))
{
// do stuff here
}
else
{
errorMessage = "User does not have delete permission";
}
var error = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content =
new StringContent(
errorMessage)
};
return error;
}
Just wanted to add to #Richards comment for using the [DnnModuleAuthorize(PermissionKey = "DELETEDATA")] for custom permissions.
The full attribute should be:
[DnnModuleAuthorize(PermissionKey = "DELETEDATA", AccessLevel = SecurityAccessLevel.Edit)]
Leaving it blank does nothing as shown here: https://github.com/dnnsoftware/Dnn.Platform/blob/f4a5924c7cc8226cfe79bbc92357ec1a32165ada/DNN%20Platform/Library/Security/Permissions/PermissionProvider.cs#L810
I guess you require a plugin that allows you to construct GET and POST APIs. you can use this plugin I found on the DNN store. https://store.dnnsoftware.com/dnn-rest-api-custom-api-authentication-authorization.
So, I'm trying to build a 'blog gateway' in Drupal 7 that will let users add/edit/remove feeds from their own external sites to a collective stream for rating, comments etc.
The site should do something along the lines of:
Let the user add a URL for their feed via the registration form
Import the most recent posts from the feed upon registration (and afterwards continually).
Create a node for each item in the feed, which can be rated by other users.
Create lists from these nodes.
Okay, so Views in combination with the Feeds module can do most of this (import feeds, create nodes for each item and create lists). I've managed to allow users to import feeds 'manually' using the Feeds module.
The problems are that I can't find a way to limit the number of feeds a user can import, and I can't figure out how to make a URL from the registration form the basis of an import.
I've been trying to solve these problems using the Rules module, but with no results.
Methinks something along the lines of the Profile Blog Information module could do the trick, but it's only available for Drupal 6 and doesn't work with the Feeds module.
Any thoughts on solutions or alternatives?
I can't find a way to limit the number of feeds a user can import
If you use the default settings of the Feeds module, you should have a "Feed" content type already. So, if you want to control the number of "feeds" a user can import, http://drupal.org/project/node_limit should do the trick, I think. It'll help you set the number of nodes of the "Feed" content type that a user can create (which should be the same thing, right?)
how to make a URL from the registration form the basis of an import
Now, I'm going to suggest a custom module route here. It should do the trick but you may/may not want to add a custom module depending on the nature of your project/your level of expertise with Drupal.
Add a URL field (probably an instance of the URL field type) to the user profile. Check to allow it to be shown in the registration form. Lets say we call it feed_url.
Implement hook_user_insert() which is called when a new user is added to the site:
/**
* Implements hook_user_insert().
*/
function MY_MODULE_user_insert(&$edit, $account, $category) {
if (isset($edit['feed_url'])) {
$index = array('feed_url', LANGUAGE_NONE, 'value');
// Get the feed URL from the registration form values.
$feed_url = drupal_array_get_nested_value($edit, $index);
if (!empty($feed_url)) {
// Create a new feed on behalf of the user
_MY_MODULE_create_feed($feed_url, $account->uid);
}
}
}
/**
* Creates a new feed on behalf of a user.
*
* #param string $url
* Feed URL
* #param numeric $uid
* {users}.uid of the user for whom this feed is being created.
*/
function _MY_MODULE_create_feed($url, $uid) {
// #todo Add more validations here
$node = new stdClass();
$node->uid = $uid;
$node->type = 'feed';
$node->feeds['FeedsHTTPFetcher']['source'] = $url;
$node->language = language_default('language');
node_save($node);
}
In my application I need do some kind of "auto login" logic at the beginning of app work. In this "auto login" function I do many actions, and one of them - setting cookie, using CookieComponent.
When I use this autoLogin in controller or component - all is fine, BUT cookies are NOT set when I do the same from dispatcher filter.
I dig deep into CakePHP code, and found that when I try set cookie from dispatcher filter, $_cookies property of CakeResponse are empty. So it's looks like dispatcher filter creates own CakeResponse, and it resets later, so cookie are not set.
My filter looks like this:
class UserLoadFilter extends DispatcherFilter
{
public $priority = 8;
public function beforeDispatch($event) {
App::uses('AuthComponent', 'Controller/Component');
if (!AuthComponent::user()) {
App::uses('CustomAuthComponent', 'Controller/Component');
//$controller = new AppController($event->data['request'], $event->data['response']);
$auth = new CustomAuthComponent(new ComponentCollection(), Configure::read('Auth'));
$auth->autoLogin();
}
}
}
I also tried set cookie directly in beforeDispatch method in this way:
App::uses('CookieComponent', 'Controller/Component');
$cookie = new CookieComponent(new ComponentCollection());
$cookie->write('test', 'TEST!', false, "1 day");
but this has no sense too.
What do I do wrong? Maybe I just don't see some simple things, but I spent many time and still can't fix this. Is it's possible at all to set cookie from this place?
Sure I can try just use setcookie, or write own cookie wraper, but I want to do it in CakePHP style, using cookie component.
This looks just wrong to me. 2.x uses authentication and authorization objects so no CustomAuthComponent - meaning the component part - is needed. Instead you create customized authentication and authorization objects.
I see no reason why this has to be done in beforeDispatcher(). So what exactly is your goal? What kind of auth are you trying to implement?
Edit based on your comment:
Simply redirect after you identified the user as Boris said and your locale gets set (if you did it right). So just read the uuid from the cookie in beforeFilter(), get the user record based on that and use the user record to do a "manual" login and then redirect.
What have you modified in the AuthComponent?
I am working on a web app project that has been in development for long time. The app has two sides, the majority of the site is publicly accessible. However, there are sections that require the user to be logged in before they can access certain content.
When the user logs in they get a sessionid (GUID) which is stored in a table in the database which tracks all sort for data about the user and their activity.
Every page of the app was written to look if this session id variable exists or not in the querystring. If a user tries to access one of these protected areas, the app checks to see if this sessiond variable is in the querystring. If i is not, they are redirected to the login screen.
The flow of the site moves has the user moving seamlessly from secured areas to non-secured areas, back and forth, etc.
So we did a test run with the Google Custom Search and it does an awesome job picking up all our dynamic content in these public areas. However, we have not been able to figure out how to pass the sessionid along with the search results IF the user is logged in already.
Is it possible to pas querystring variables that already exist in the url along with the search results?
As far as I know, this is not possible. Google doesn't give you the possibilty to modify the URL's of the Search Results in their Custom Search.
A possible solution would be to store your Session-Key to a Cookie, rather than passing it with every URL.
Use the parseQueryFromUrl function
function parseQueryFromUrl () {
var queryParamName = "q";
var search = window.location.search.substr(1);
var parts = search.split('&');
for (var i = 0; i < parts.length; i++) {
var keyvaluepair = parts[i].split('=');
if (decodeURIComponent(keyvaluepair[0]) == queryParamName) {
return decodeURIComponent(keyvaluepair[1].replace(/\+/g, ' '));
}
}
return '';
}
Select RESULTS ONLY option in the Look & Feel and it will provide you with the code.
www.google.com/cse/