Dynamic Schema Processor (DSP) and Dynamic Role (DR) support

361 views
Skip to first unread message

vincentd...@gmail.com

unread,
Oct 3, 2016, 11:30:49 AM10/3/16
to Saiku Dev
Hello,

I am evaluating Saiku EE 3.8 standalone for my company. Most of our required features are already supported by Saiku EE 3.8 making it a valid challenger to other well established (and also far more expensive) products in this market area. I just have some doubts about DSP and DR support as described in Mondrian In Action which are key features for us (we need at least one of them). 

1/ DSP support:
According to https://groups.google.com/a/saiku.meteorite.bi/forum/#!searchin/user/dynamic/user/J1yqTXnHPE4/fwuCK33bBwAJ and http://jira.meteorite.bi/browse/SKU-1438, DSP is not fully supported in Saiku standalone but might be with Saiku as a Pentaho BI plugin. Do you confirm this ? Will SKU-1438 be solved in 3.9 and when do you plan to publish this release ?

2/ Dynamic Role support as described in Mondrian in action depends on a Pentaho BI server class named MDXConnection. This class aims to provide a DelegatingRole role to the connection. When I look at SecurityAwareConnectionManager and SaikuMondrianHelper classes code, it seems a good place to start if one expects to implement such a use case (I can provide my own implementation of SecurityAwareConnectionManager in saiku-beans.xml). Am I right ? Do you think I may encounter some problems with a connection cache or something like that ?  
 

Thanks,
Vincent 

Tom Barber

unread,
Oct 3, 2016, 11:44:32 AM10/3/16
to vincentd...@gmail.com, Saiku Dev
Hi DSP's are supported, you just need to use the advanced data source.

For #2 you seem to be on the right track, there is a property somewhere for per user/connection stuff although I forget its name.

Tom

vincentd...@gmail.com

unread,
Oct 6, 2016, 3:58:02 PM10/6/16
to Saiku Dev, vincentd...@gmail.com
Thanks  for your quick reply. It seems that I have to implement something like PentahoSecurityAwareConnectionManager in order
to have a connection per user and avoid mondrian's caches side-effects in both scenarios (my DSP's behavior depends on the user).
The drawback is that I will have an impact on the global cache size, mainly due to the segment cache. There is no out of the box
support for distributed cache in Saiku standalone, but it seems quite easy to plug a Redis external cache via the Segment Cache SPI
I will give it a try ASAP and share my feed back.

Vincent


vincentd...@gmail.com

unread,
Oct 25, 2016, 4:07:15 AM10/25/16
to Saiku Dev, vincentd...@gmail.com
Things were simpler than I thought: when using security.enabled=true, Saiku manages automatically a pool with one connection by user, avoiding cache side-effects I mentioned in my previous message. 
Then I implemented my own IDatasourceManager in order to add the ability to provide IConnectionProcessor support.
To achieve this, I used the default RepositoryDatasourceManager and add the IConnectionProcessor support as in PentahoDatasourceManager
With the IConnectionProcessor support at hand, I have a hook allowing me to implement a dynamic role behaviour as in Mondrian In Action (by extending DelegatingRole).
I found this way less intuitive than the DSP one, though, it's better to have two ways to achieve a similar goal than just one.
Finally, I easily managed to use Redis as an external Segment cache by adapting a bit the code I pointed out in my previous message.

Regards
Vincent

public class CustomRoleProcessor implements IConnectionProcessor {
       
private final static Logger logger = Logger.getLogger(CustomRoleProcessor.class);

       
public ISaikuConnection process(ISaikuConnection con) {
               
if (con != null && ISaikuConnection.OLAP_DATASOURCE.equals(con.getDatasourceType())
                               
&& con.getConnection() instanceof OlapConnection) {
                       
OlapConnection olapCon = (OlapConnection) con.getConnection();
                       
try {
                               
RolapConnection rCon = olapCon.unwrap(RolapConnection.class);
                               
if (rCon.getSchema().lookupCube("telephonie", false) != null) {
                                       
Role authRole = rCon.getSchema().lookupRole("Authenticated");
                                       
if(logger.isDebugEnabled())
                                                logger
.debug("rCon.getSchema().lookupRole('Authenticated') ==>" + authRole);
                                       
                                       
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                                       
Object principal = auth.getPrincipal();
                                       
if(principal instanceof UserDetails) {
                                               
UserDetails userDetails = (UserDetails) principal;
                                               
String login = userDetails.getUsername();
                                               
if(authRole !=  null && !login.equals("admin")) {
                                                       
CustomRoleDelegate customRole = new CustomRoleDelegate(authRole);
                                                        rCon
.setRole(customRole);
                                               
}
                                       
}
                                       
                                       
                               
}
                       
} catch (SQLException e) {
                                logger
.error("Unable to unwrap Olap connection", e);
                       
}
               
}

               
return con;
       
}

}



public class CustomRoleDelegate extends DelegatingRole {
private final static Logger logger = Logger.getLogger(CustomRoleDelegate.class);
private static String HIERARCHY_NAME = "equipes";
private static String GrantAll = "ALL";
private String agence;
private String collaborateur;
public CustomRoleDelegate(Role role) {
super(((RoleImpl) role).makeMutableClone());
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
if(principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
String login = userDetails.getUsername();
logger.warn("User logged in for CustomRoleDelegate : " + login);
if("smith".equalsIgnoreCase(login)) {
this.agence = "Paris Haussmann";
this.collaborateur = "Eric";
} else if("wesson".equalsIgnoreCase(login)) {
this.agence = "Paris Haussmann";
this.collaborateur = GrantAll;
}
}
}

@Override
public HierarchyAccess getAccessDetails(Hierarchy hierarchy) {
HierarchyAccess ha = super.getAccessDetails(hierarchy);
return (ha == null ? null : new CustomHierarchyAccess(ha));
}

protected class CustomHierarchyAccess extends RoleImpl.DelegatingHierarchyAccess {
public CustomHierarchyAccess(HierarchyAccess ha) {
super(ha);
}

public Access getAccess(Member member) {
return CustomRoleDelegate.this.getAccess(member, hierarchyAccess.getAccess(member));
}
}

@Override
public Access getAccess(Hierarchy hierarchy) {
return role.getAccess(hierarchy);
}

@Override
public Access getAccess(Member member) {
return getAccess(member, role.getAccess(member));
}

protected Access getAccess(Member member, Access access) {
String memberHierarchyName = member.getHierarchy().getName();
if (memberHierarchyName.contains(HIERARCHY_NAME)) {
if (member.getUniqueName().equalsIgnoreCase("[equipe].[equipes].[" + this.agence + "].[" + this.collaborateur + "]")) {
return Access.ALL;
}
if (member.getUniqueName().equalsIgnoreCase("[equipe].[equipes].[" + this.agence + "]") && this.collaborateur == GrantAll) {
return Access.ALL;
}
for (Member mem : member.getAncestorMembers()) {
if (mem.getUniqueName().equalsIgnoreCase("[equipe].[equipes].[" + this.agence + "]") && this.collaborateur == GrantAll) {
return Access.ALL;
}
}
Access acc = (access == Access.CUSTOM) ? access : Access.NONE;
return acc;
}
return access;
}

@Override
public Access getAccess(Level level) {
return role.getAccess(level);
}
}



Reply all
Reply to author
Forward
0 new messages