I am developing a CRUD based database which needs to have some ACL (Access Control Lists) based on users. We want to make it so that there's an Admin access panel as well so we can quickly delete content if needed or otherwise moderate it. I am trying to keep it flexible so that it could be a separate interface for convenience but also not diverge from the general user interface because otherwise it will be harder to maintain.
What would be a good way to handle creating a user interface in the front end, assuming the backend will deal with the ACLs? Is it necessary to create a separate BB.js interface to serve or is it fine to pass a bit of extra code to all users that will be ignored?
Recommendations or warnings would be appreciated too!
I interpreted your question as you want to serve up different interfaces for different access levels of users. Therefore I would:
Put a check for the users access before they hit each route to make sure they aren't accessing a page they shouldn't see
They can still try to circumvent this (since they can change their user model), your backend would still catch any unauthorized requests.
You can also conditionally show and hide page elements based on user access levels.
Heres an example code for my ACL.coffee
acl = {}
acl['admin'] = [
'page1',
'page2',
'page3',
'page4',
'page5'
]
acl['user'] = [
'page1',
'page2',
'page3'
]
hasAccess = (route) ->
# Get User Model
user = window.App.user
# Get Associated ACL
permissions = acl[user.get('role')]
# Check each URL for Access Privileges
# Returns false if route not in array
permissions.some (r) -> ~route.indexOf r
{hasAccess}
Related
We want to display customer (actually customer-group) specific information on product detail pages in Shopware 6.
There seems to be the HTTP cache and we are afraid that the page would be cached if a specific customer group displays the page and the information would be leaked to non-customers.
Is this assumption correct?
The documentation does not reveal much information about this.
Is there a way to set specific cache tags, so that the information is only displayed to the correct customer group?
Or do we need to fetch the data dynamically via AJAX?
Bonus question: Can the HTTP cache be simulated in automatic tests to ensure the functionality works?
What I found out so far:
The is annotation #httpCache for controller, which seems to control whether a page is cached or not
The cache key is generated in \Shopware\Storefront\Framework\Cache\HttpCacheKeyGenerator::generate. It take the full request URI into account, and some cacheHash which is injected. I believe it would not take the customer group into account
Maybe this generate() method could be decorated, but I am not sure if that is the right way.
There is a cookie being set sw-cache-hash which influences the caching. It takes the customer into account.
sw-cache-hash is created here:
if ($context->getCustomer() || $cart->getLineItems()->count() > 0) {
$cookie = Cookie::create(self::CONTEXT_CACHE_COOKIE, $this->buildCacheHash($context));
$cookie->setSecureDefault($request->isSecure());
$response->headers->setCookie($cookie);
} else {
$response->headers->removeCookie(self::CONTEXT_CACHE_COOKIE);
$response->headers->clearCookie(self::CONTEXT_CACHE_COOKIE);
}
So as soon you are logged in or have some items in the cart, a different cache hash is used. This depends on the following, but not on the customer group it self:
private function buildCacheHash(SalesChannelContext $context): string
{
return md5(json_encode([
$context->getRuleIds(),
$context->getContext()->getVersionId(),
$context->getCurrency()->getId(),
]));
}
Additionally there is the notion of cache-invalidation states, that describe when the caching should not be used.
You can configure that inside the shopware.yaml config file for the http-cache as a whole or on route level for the store-api routes.
From the default config inside platform:
shopware:
cache:
invalidation:
http_cache: ['logged-in', 'cart-filled']
product_listing_route: []
As you can see by default the http-cache won't be used if a user logs in or has something in his cart.
As you can see in the last code snippet, it takes into account the active Rule ids.
This means that if you create a rule (through Settings > Rule Builder) that is active on a certain group, but not on another or no group, it will be taken into account & create a different cache hash for the different customer groups.
I've been developing actions using the Dialogflow API. I get how to ask for a single permission (like SIGN_IN, PERMISSION, etc.) at a time, but I wanted to know how to ask multiple permissions at the same time? Does one try them sequentially (i.e. ask for one and once it's accepted or rejected, try the next), or is there a way through which multiple permissions can be asked concurrently (maybe like askPermissions('SIGN_IN', 'PERMISSION'))?
The use case is that the action requires multiple permissions before it can be successfully executed.
Thanks!
Permissions is an array, so it can take more than 1 string. Like this:
const options = {
context: 'Whatever text you add here will explain to the user why you're asking to use their location.',
permissions: ['DEVICE_PRECISE_LOCATION','NAME'],
};
conv.ask(new Permission(options));
Using Orchard 1.6 Iv created a new role 'FactoryWorker'. When this user logs in from the front end I want them to be navigated to one page only.
OrchardLocal/System/ManufacturedProducts
I have set this page to be a print screen of the order details so the factory worker will know what products to get ready for ship out & they wont be able to navigate as no menu appears, but also need the other pages blocked incase the user decides to enter the URL of a page they arnt allowed access to.
This is the only page I want this particular user to be able to access(after they login), and I have added a logout button, which logs out the user and returns them to the home page.
So iv been looking through editing a role, with permissions and content etc...but this all seems to be applying to forms and content in general. where the user can access any content type etc...
So can someone advise me on how to do this?
thanks for any replies
UPDATE
I forgot to mention that this is not a content type, item or part I am talking about.
I have created my own controller & View & VM which is accessible from the dash board (using the AdminMenu, which brings the admin user to OrchardLocal/System/ManufacturedProducts)
I have looked at Orchard.ContentPermissions Feature but it only seems to allow me to 1)Grant permissions for others or 2)Grant permission for own content
any ideas?
You can use a Request Filter, (I do not know if it is the best way) :
FilterProvider – defines the filter applied to each request. Resembles the way default ASP.NET MVC action filters work with the difference that it’s not an attribute. All FilterProvider objects are injected into the request pipeline and are applied to all requests (so you need to check if the current request is suitable for your filter at the beginning of an appropriate method).
From : http://www.szmyd.com.pl/blog/most-useful-orchard-extension-points
So you could implement something like this
public class Filter : FilterProvider, IAuthorizationFilter {
private readonly IAuthenticationService _authenticationService;
public Filter(IAuthenticationService authenticationService) {
_authenticationService = authenticationService;
}
public void OnAuthorization(AuthorizationContext filterContext) {
//If route is the restricted one
if (filterContext.HttpContext.Request.Url.AbsoluteUri.Contains("OrchardLocal/System/ManufacturedProducts")) {
//Get the logged user
IUser loggedUser = _authenticationService.GetAuthenticatedUser();
if (loggedUser == null)
return filterContext.Result = new HttpUnauthorizedResult();
//Get the Roles
var roles = loggedUser.As<IUserRoles>().Roles;
if (!roles.Contains("FactoryUser")) {
//User is not authorized
return filterContext.Result = new HttpUnauthorizedResult();
}
}
}
}
Note: Untested code!
EDIT: Also you could invert the logic and check if the logged user has the role 'FactoryUser' and restrict its access to every page except the one they should see.
Your module can create a new permission (look at one of the permissions.cs files for examples), then create a role that has only that permission. Have your controller action check that permission (again, many examples found by finding usage of the permissions defined in one of the permissions.cs).
You can use the Content Permissions module. Using this module you can attach a content item permission part to a content type. This part allows you to choose which roles can see the content when you create it.
Note: If it's any help, I'm using Pyramid 1.3.2. I know it's a little out of date, I would prefer not to update right away, but I might be able to force an update if the latest version provides better support for this use case.
The Pyramid-based application I'm working on has a strict authorization policy: all calls must be authenticated. Since 1) it's tedious to add this manually on every request handelr; and 2) we don't want anybody to "forget" adding authentication, we enforce this server-wide using a simple Pyramid middleware (tween) that verifies all incoming requests.
Recently, this restriction has been slightly relaxed: occasionally, some resources should support (safe & idempotent) GET without authentication.
It seems this is directly opposed to the usual design ideas behind authentication in most web frameworks (optional authentication), so I can't get it to work quite as expected.
QUESTION: What is the correct approach to implementing an authorization middleware that authenticates & verifies authorization by default, but can be disabled on a view-by-view basis?
So far, I've tried adding a simple decorator like so:
def allows_anonymous_access(f):
f.allows_anonymous_access = True; return f
#allows_anonymous_access
def my_pyramid_view(request):
# ...
In my middleware, I would like to use it like this:
def authorization_middleware(handler, registry):
def verify_authorization(request):
# Identify the user making the request. Make sure we get the
# user's identify if provided, even when the request handler
# allows anonymous access.
try:
request.principal = extract_user(request)
except InvalidCredentials, error:
if getattr(handler, 'allows_anonymous_access', False):
request.principal = AnonymousUser()
else:
raise HTTPUnauthorized(...)
# Invoke the handler.
return handler(request)
# Middleware that will pre/post-process the request.
return authorization_middleware
However, when the middleware executes, handler is not my view. It happens to be a bound method (pyramid.router.Router.handle_request) which does not provide me access to the view callable, meaning I cannot access the flag set by the middleware.
You probably want pyramid.config.set_default_permission(permission). From docs:
Adding a default permission makes it unnecessary to protect each view
configuration with an explicit permission, unless your application
policy requires some exception for a particular view.
If a default permission is in effect, view configurations meant to
create a truly anonymously accessible view (even exception view views)
must use the value of the permission importable as
pyramid.security.NO_PERMISSION_REQUIRED. When this string is used as
the permission for a view configuration, the default permission is
ignored, and the view is registered, making it available to all
callers regardless of their credentials.
Answer provided by raydeo_ on #pyramid freenode IRC channel.
I'm having trouble finding documentation on the request object argument used in replication filters ('req' in the sample below):
function(doc, req) {
// what is inside req???
return false;
}
This old CouchBase blog post has a little code snippet that shows the userCtx variable being a part of the request object:
What is this userCtx? When you make an authenticated request against
CouchDB, either using HTTP basic auth, secure cookie auth or OAuth,
CouchDB will verify the user’s credentials. If they match a CouchDB
user, it populates the req.userCtx object with information about the
user.
This userCtx object is extremely useful for restricting replication of documents to the owner of the document. Check out this example:
function(doc, req) {
// require a valid request user that owns the current doc
if (!req.userCtx.name) {
throw("Unauthorized!");
}
if(req.userCtx.name == doc.owner) {
return true;
}
return false;
}
But the problem now is that CouchDB requires the filter method to be explicitly chosen by the initiator of the replication (in this case, the initiator is a mobile user of my web app):
curl -X POST http://127.0.0.1:5984/_replicate \
-d '{"source":"database", \
"target":"http://example.com:5984/database", \
"filter":"example/filtername"
}'
The Question
Is there a way to enforce a specific filter by default so that users are restricted to replicating only their own data? I'm thinking the best way to do this is to use a front end to CouchDB, like Nginx, and restrict all replication requests to ones that include that filter. Thoughts? Would love a way to do this without another layer in front of CouchDB.
Data replication stands right with user ability to read data. Since if your users shares data within single database all of them has right to replicate all of them to their local couches. So you couldn't apply any documents read restriction unless you've split single shared database into several personal ones - this is common use case for such situations.
There is no any way to enforce apply changes feed filter or other parameters like views has. However, you can use rewrites to wraps requests to some resources with predefined query parameters or even with dynamic ones. This is a little not solution that you'd expected, but still better that nginx and some logic at his side: probably, you'd to allow users to specify custom filters with custom query parameters and enforce you're own only if nothing specified, right?
P.S. Inside req object is very useful about current request. Partially it was described at wiki, but it's a little out of date. However, it's easily to view it with simple show function:
function(doc, req){
return {json: req}
}