Permission control is one of the most important requirements in an enterprises system, whether it is for internal or external use.
We assume that most companies/organizations already have their own authentication and authorization system. And Keikai provides 3 kinds of API allowing you to integrate Keikai with your existing security system to take full permission control. The 3 APIs are:
- Hide/show UI including the toolbar, formula bar, sheet bar, and context menu.
- Disable available features e.g. adding, deleting a sheet.
- Protect sheets and set available actions e.g. formatting cells or inserting rows.
For details, please refer to corresponding sections in Keikai Developer Reference.
<?component name="roleButton" inline="true" macroURI="roleButton.zul"?>
<zk>
<style>
.roleButton{
min-width:260px;
border: 1px solid #CFCFCF;
display:inline-block;
margin:0px 15px 0px 15px;
cursor:pointer;
}
.roleButton div{
display:inline-block;
vertical-align: middle;
padding:0px 0px 0px 10px;
}
.role{
min-width: 70px;
color: #3da7ed;
line-height: 60px;
}
.role-description{
max-width: 180px;
word-break: break-word;
}
.selectedButton{
background-color: #538FCC;
color: white;
border: none;
}
.selectedRole{
background-color: #34829E;
color: white;
}
</style>
<div vflex="1" apply="demo.permission.PermissionComposer">
<groupbox width="100%" contentStyle="padding: 10px 0px 20px 30px">
<caption label="Roles" sclass="subtitle">
</caption>
<roleButton id="defaultButton" role="OWNER" description="All operations allowed."/>
<roleButton role="EDITOR" description="Read and edit, but cannot add/remove sheets, etc."/>
<!-- Read and edit, but cannot add/remove sheets, etc. -->
<roleButton role="VIEWER" description="View only."/>
</groupbox>
<include id="zssArea" src="/demo/permission/spreadsheet.zul" vflex="1"/>
</div>
</zk>
<zk >
<div id="${arg.id}" sclass="roleButton ">
<div sclass="role">
<label value="${arg.role}" />
</div>
<div sclass="role-description">
${arg.description}
</div>
<custom-attributes role="${arg.role}"/>
</div>
</zk>
package demo.permission;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.Selectors;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import io.keikai.ui.Spreadsheet;
import org.zkoss.zul.Button;
import org.zkoss.zul.Div;
import org.zkoss.zul.Include;
import org.zkoss.zul.Label;
public class PermissionComposer extends SelectorComposer<Component> {
@Wire
private Label roleName;
@Wire
private Include zssArea;
@Wire
private Div defaultButton;
private Div currentSelectedButton;
final static String ZSS_ID = "spreadsheet";
private Role.Name loginRole = Role.Name.OWNER; //current login role
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
selectRoleButton(defaultButton);
focusSpreadsheet();
}
@Listen("onClick = .roleButton")
public void login(Event event){
Div clickedButton = (Div)event.getTarget();
String roleName = clickedButton.getAttribute("role").toString();
Role.Name role = Role.Name.valueOf(roleName);
if (loginRole != role){
unselectRoleButton(currentSelectedButton);
selectRoleButton(clickedButton);
loginRole = role;
//reload the zss to reset permission
zssArea.setSrc(null);
zssArea.setSrc("/demo/permission/spreadsheet.zul");
if (role != Role.Name.OWNER){
AuthorityService.applyPermission((Spreadsheet)zssArea.getFellow(ZSS_ID), role); //apply the role's permission
}
}
focusSpreadsheet();
}
private void focusSpreadsheet(){
((Spreadsheet)Selectors.find(zssArea, "#spreadsheet").get(0)).focus();
}
//css class
final static String ROLE_BUTTON = "roleButton";
final static String SELECTED_BUTTON = "selectedButton";
final static String ROLE = "role";
final static String SELECTED_ROLE = "selectedRole";
private void selectRoleButton(Div button){
currentSelectedButton = button;
button.setSclass(ROLE_BUTTON+" "+SELECTED_BUTTON);
((Div)currentSelectedButton.getFirstChild()).setSclass(ROLE+" "+SELECTED_ROLE);
}
private void unselectRoleButton(Div button){
button.setSclass(ROLE_BUTTON);
((Div)currentSelectedButton.getFirstChild()).setSclass(ROLE);
}
}
package demo.permission;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.keikai.api.*;
import io.keikai.ui.AuxAction;
import io.keikai.ui.Spreadsheet;
import demo.permission.Permission.NAME;
/**
* All permissions are granted by default. We need to specify which is ungranted for each role.
*/
public class AuthorityService {
static private Map<Role.Name, Role> roles = new HashMap<Role.Name, Role>();
/**
* Initialize role-permission settings.
*/
static{
Role owner = new Role(Role.Name.OWNER);
Role editor = new Role(Role.Name.EDITOR);
Role viewer = new Role(Role.Name.VIEWER);
editor.assign(new Permission(NAME.SHEET_ADD, false) {
@Override
void apply(Spreadsheet ss) {
ss.disableUserAction(AuxAction.ADD_SHEET, !granted);
}
});
editor.assign(new Permission(NAME.SHEET_DELETE, false) {
@Override
void apply(Spreadsheet ss) {
ss.disableUserAction(AuxAction.DELETE_SHEET, !granted);
}
});
editor.assign(new Permission(NAME.SHEET_MOVE, false) {
@Override
void apply(Spreadsheet ss) {
ss.disableUserAction(AuxAction.MOVE_SHEET_LEFT, !granted);
ss.disableUserAction(AuxAction.MOVE_SHEET_RIGHT, !granted);
}
});
editor.assign(new Permission(NAME.SHEET_HIDE, false) {
@Override
void apply(Spreadsheet ss) {
ss.disableUserAction(AuxAction.HIDE_SHEET, !granted);
ss.disableUserAction(AuxAction.UNHIDE_SHEET, !granted);
}
});
editor.assign(new Permission(NAME.SHEET_RENAME, false) {
@Override
void apply(Spreadsheet ss) {
ss.disableUserAction(AuxAction.RENAME_SHEET, !granted);
}
});
editor.assign(new Permission(NAME.SHEET_COPY, false) {
@Override
void apply(Spreadsheet ss) {
ss.disableUserAction(AuxAction.COPY_SHEET, !granted);
}
});
editor.assign(new Permission(NAME.SHEET_PROTECT, false) {
@Override
void apply(Spreadsheet ss) {
ss.disableUserAction(AuxAction.PROTECT_SHEET, !granted);
}
});
for (Permission p : editor.getPermissions()){
viewer.assign(p);
}
viewer.assign(new Permission(NAME.TOOLBAR, false) {
@Override
void apply(Spreadsheet ss) {
ss.setShowToolbar(granted);
}
});
viewer.assign(new Permission(NAME.FORMULABAR, false) {
@Override
void apply(Spreadsheet ss) {
ss.setShowFormulabar(granted);
}
});
viewer.assign(new Permission(NAME.CONTEXT_MENU, false) {
@Override
void apply(Spreadsheet ss) {
ss.setShowContextMenu(granted);
}
});
SheetProtection READ_ONLY_WITH_SELECTION = SheetProtection.Builder.create().
withSelectLockedCellsAllowed(true).withSelectUnlockedCellsAllowed(true).build();
viewer.assign(new Permission(NAME.EDIT, false) {
@Override
void apply(Spreadsheet ss) {
for (int i=0 ; i < ss.getBook().getNumberOfSheets() ; i++){
Ranges.range(ss.getBook().getSheetAt(i)).protectSheet(READ_ONLY_WITH_SELECTION);
}
}
});
roles.put(Role.Name.OWNER, owner);
roles.put(Role.Name.EDITOR, editor);
roles.put(Role.Name.VIEWER, viewer);
}
static public List<Role.Name> getRoles(){
return Arrays.asList(Role.Name.values());
}
static public void applyPermission(Spreadsheet ss, Role.Name name){
Role role = roles.get(name);
for (Permission p : role.getPermissions()){
p.apply(ss);
}
}
}
package demo.permission;
import io.keikai.ui.Spreadsheet;
/**
* An approval of a mode of access to a resource.
*
* @author hawk
*
*/
abstract public class Permission implements Comparable<Permission> {
enum NAME{TOOLBAR, FORMULABAR, CONTEXT_MENU, SHEETBAR,
SHEET_ADD, SHEET_DELETE, SHEET_MOVE, SHEET_RENAME, SHEET_HIDE, SHEET_COPY, SHEET_PROTECT,
EDIT}
protected NAME name;
protected boolean granted;
public NAME getName() {
return name;
}
public boolean isGranted() {
return granted;
}
public Permission(NAME name, boolean granted){
this.name = name;
this.granted = granted;
}
/**
* Apply this permission on the resource
*/
abstract void apply(Spreadsheet ss);
@Override
public int compareTo(Permission p) {
return this.name.compareTo(p.getName());
}
}
package demo.permission;
import java.util.Set;
import java.util.TreeSet;
/**
* Role is a job function or title which defines an authority level.
* @author hawk
*
*/
public class Role {
public enum Name {OWNER, EDITOR, VIEWER};
private Name name;
private Set<Permission> permissions = new TreeSet<Permission>();
public Role(Name name){
this.name = name;
}
public void assign(Permission p){
permissions.add(p);
}
public Name getName() {
return name;
}
public Set<Permission> getPermissions() {
return permissions;
}
}