I'm building a web application using symfony2. I have different types of users with different roles; ROLE_STUDENT and ROLE_TEACHER, those two user can access a course's details; if the user is a teacher, a button edit is shown and if it's the student then a button subscribe will be shown, and actually this is not secure because it just hides the path to the controllers action, if the student types in the address bar /course/2/edit the edit action would be executed so I had to secure the action using #security annotation:
This is what I have done so far:
/**
* #Security("has_role('ROLE_TEACHER')")
*/ public function editAction()
{}
and in twig :
{% if is_granted('ROLE_TEACHER') %}
edit
{% elseif is_granted('ROLE_STUDENT')%}
subscribe
.
The problem is that I have a lot of accessible content to both users and I think there is a better solution to this instead of copy/past the same code all over. I'm new to Symfony 2, please bear with me.
There are multiple ways to achieve this but what you are doing is not wrong.
One way to achieve this is to set ROLE for the ROUTES so that ROLE_STUDENT roles can only access URLs that will be something like this website.com/students and ROLE_TEACHER can only access website.com/teachers
access_control:
- { path: ^/student/, roles: ROLE_STUDENT }
- { path: ^/teamleader/, roles: ROLE_TEACHER }
You can then set the edit route only for teachers like website.com/teachers/course/2/edit this way no edit route is going to be available for ROLE_STUDENT and they will get 404 error or access denied error if they try to access teacher route. You can do the same for the subscribe feature.
Like I said there are more ways to achieve this and this is one of them.
Related
I have a multi-tenant system but I don't know how to go about setting css based on the current tenant. The tenant can either be a sub-domain or be set once a user logs in. but how would I go about setting up the css for this?
Tenant styles such as primary colour, secondary colour are saved in a table tenant_styles. I have created the model for this and set the following in the Styles Model:
public function tenant() {
return $this->belongsTo(Tenant::class);
}
and in the Tenant Model:
public function styles() {
return $this->hasOne(Styles::class);
}
I can get the tenant styles using $tenant->styles
I'm not sure on how to set up the css stylesheet so that I can use the tenant styles. I need it so that if the tenant is the subdomain, the css is set on the login page.
but then if the tenant is not a subdomain, use the default colours and then once the user has logged in set the tenant colours.
I need to implement field-level permissions in a Page model, in a SilverStripe 3.2 website.
Let's imagine I have an ArticlePage.php model. It has the usual fields like $MenuTitle and $Content, and I've added other properties like $Subtitle and $Author.
I can protect the whole model by using providePermissions() and the associated canEdit() methods, but I need to protect individual fields / page properties.
What I need to do is:
Admins should be able to edit all fields
Users in another permissions group should only be able to edit and save $Subtitle
Is this possible in SilverStripe 3.2? Is there a SilverStripe way of doing it?
If not, is there a way I can Identify the user group of the current user and then perhaps conditionally show the $field->addFieldToTab() code? Is it possible to stop the user saving a field by posting the data maliciously, perhaps by adding the missing fields via inspector?
Thanks in advance.
So here's my own answer. This post was helpful: https://www.silverstripe.org/community/forums/customising-the-cms/show/11693
You can conditionally show CMS fields and tabs using code like the post demonstrates:
public function getCMSFields()
if(!Permission::check('PERMISSION_LABEL'){
$fields->removeFieldFromTab("Root.Main","MenuTitle");
$fields->removeByName('BannerImages');
// etc...
}
// etc...
}
Having defined the permission:
public function providePermissions()
{
return array(
'PERMISSION_LABEL' => 'Can edit some fields',
);
}
My concern with this approach was that a user could still create a form field on the page using inspector or JS and submit values for fields they should not be able to see.
Having tested this it appears that field values are not saved if they are not listed on the page, but are sent with the POST data. Although I'd love to know if a SilverStripe expert could confirm that.
I'm a bit new to Symfony, but I've got an easy to explain situation:
I've got a public home page, and a private home page. I'd like to have both of these accessible with the URL "/"
When a non-authenticated person visits the address www.example.com/ I'd like for them to be routed to PublicController::indexAction()
When an authenticated user visits the address www.example.com/ I'd like for them to be routed to Privatecontroller::indexAction()
Is this possible?
(symfony 2.7 btw)
Definitely possible, although the details depend on what you're doing in each controller action. The easiest way would be to have:
class PublicController extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
public function indexAction()
{
if ($this->getUser() !== null) {
return $this->forward('BundleName:PrivateController:index');
}
// do public controller details
}
}
So by default everyone is sent to PublicController:indexAction which does a check to see if there is a logged in user (using the getUser method from Symfony's Controller class) and if there is, forward the request over to PrivateController:indexAction. If not, then it just shows the public action as expected. You could invert this if you're expecting more logged in than logged out users as there will be a performance penalty for forwarding (as Symfony will create and dispatch a subrequest).
The longer answer is understanding what you're doing in each controller that requires them to be separate and whether you could combine the functionality into a service or otherwise re-architect them. Without knowing more about your specific problem domain, the above seems like the best way forward.
Got a social network startup running on Symfony (always using latest versions) so naturally I encountered this challenge of showing different content on the homepage dependent on first, your loggedin or not status, and second if logged in different personalized content dependent on your logged in user id. Although the answer above works, I found it much better and performant to use twig to display which content because I could use the render_esi tag to use a reverse proxy cache and avoid not just database lookups but template generation and the whole request hitting Symfony.
For example
{# src/MyApp/AppBundle/Resources/views/Page/index.html.twig #}
{% extends 'MyAppAppBundle::layout.html.twig' %}
.....
{% block body %}
{% if not app.user %}
Code for non-logged in user
e.g. {{ render_esi(controller('MyAppAppBundle:Home:non_logged_in_user')) }}
{% else %}
Code for logged in user
e.g {{ render_esi(controller('controller('MyAppAppBundle:Home:logged_in_user', { 'user': app.user.id })) }}
{% endif %}
....
{% endblock %}
I'm currently toying around with making a framework-only SilverStripe site and so far so good - I've set up a controller and some models and all is well there, however I'm having issue with creating a login system.
It seems the $Form variable that normally displays the login form when you visit /admin doesn't display anything. Should it? I thought that it would, however it is not doing.
I guess my question is - do framework only sites use the default login form, and if so what are the first steps to troubleshoot why the form is not showing on my site? Could it have something to do with routes?
Here is my code:
Routes.yml
---
Name: app
After: 'framework/routes'
---
Director:
rules:
'': 'GanttController'
'$URLSegment//$Action/$ID/$OtherID': 'GanttController'
GanttController.php
<?php
class GanttController extends BaseController {
public function index() {
return $this->customise(new ArrayData(array(
'Title' => 'Gantt Chart'
)))->renderWith(array(
'GanttController',
'Page'
));
}
Page.ss
<html>
<head>
<title>$Title</title>
</head>
<body>
<div class="header">
<h1>Gantt</h1>
</div>
<div class="pane">
$Layout
$Form
</div>
</body>
</html>
If I add the line 'admin': 'AdminRootController' to my YAML routes and go to /admin, instead of it loading up my project it loads up the get started with the SilverStripe framework page, where it links you to the docs on adding controllers/templates.
the framework should display the $Form given that you have a template that uses it.
the framework is still designed to have a frontend, this means to 2 things:
framework will use templates/Page.ss as default template for frontend as usual
if you want the admin / or the secuirty login form, you need to access this controller
you can do this 3 ways:
access it via the already existing route: /admin or /Security/login?backURL=/the/url/to/redirect/to/after/login
use a http redirect make / redirect to /admin
if you don't want a frontend at all, you just use Page.ss for the login template and make / to show the same thing as /admin by assiging the route / to the AdminRootController with the following yml config:
File: mysite/_config/routes.yml
---
Name: mysiteroutes
Before: '*'
After:
- '#rootroutes'
- '#coreroutes'
- '#modelascontrollerroutes'
- '#adminroutes'
---
Director:
rules:
'': 'AdminRootController'
if this does not answer your question and you still can't get the form to display, please paste your code (your route configs, your Page.ss, and any relevant Controller you might have).
We can easily embed a controller within a template in twig:
{% render "AcmeGolferBundle:Golfer:showGolfersList" %}
When we basically use a controller like this:
/**
* Lists all golfers.
*
* #Route("/golfersList", name="golfers_list")
* #Template()
*/
public function showGolfersListAction()
{
//....doStuff
}
In that case, the only use of the controller will be in that template. Is there a way to avoid the user to trigger the url directly, meaning /golferList on its own?
EDIT
The point I am trying to make is the following:
I need the user to use the controller through the template it is embedded in, but not directly via the url. I realise this might not be possible, but because the controller is embedded, it doesn't have a proper css structure. Therefore, if it is triggered via the url directly, it will look pretty ugly on the page.
Securing route by IP might be useful for you:
security:
# ...
access_control:
- { path: ^/golferList, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }