diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index b12a072c148..e665a9b3c10 100755
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -410,6 +410,12 @@
Anonymous user allowed by default
+
+ zeppelin.username.force.lowercase
+ false
+ Force convert username case to lower case, useful for Active Directory/LDAP. Default is not to change case
+
+
zeppelin.notebook.default.owner.username
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index 2960cd03faa..061e230be14 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -517,6 +517,10 @@ public boolean isAnonymousAllowed() {
return getBoolean(ConfVars.ZEPPELIN_ANONYMOUS_ALLOWED);
}
+ public boolean isUsernameForceLowerCase() {
+ return getBoolean(ConfVars.ZEPPELIN_USERNAME_FORCE_LOWERCASE);
+ }
+
public boolean isNotebookPublic() {
return getBoolean(ConfVars.ZEPPELIN_NOTEBOOK_PUBLIC);
}
@@ -767,6 +771,7 @@ public enum ConfVars {
// i.e. http://localhost:8080
ZEPPELIN_ALLOWED_ORIGINS("zeppelin.server.allowed.origins", "*"),
ZEPPELIN_ANONYMOUS_ALLOWED("zeppelin.anonymous.allowed", true),
+ ZEPPELIN_USERNAME_FORCE_LOWERCASE("zeppelin.username.force.lowercase", false),
ZEPPELIN_CREDENTIALS_PERSIST("zeppelin.credentials.persist", true),
ZEPPELIN_CREDENTIALS_ENCRYPT_KEY("zeppelin.credentials.encryptKey", null),
ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE("zeppelin.websocket.max.text.message.size", "1024000"),
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java
index e8a92255fc9..0c01d97ecf7 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterOption.java
@@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.List;
+import org.apache.zeppelin.conf.ZeppelinConfiguration;
/**
*
@@ -27,6 +28,7 @@ public class InterpreterOption {
public static final transient String SHARED = "shared";
public static final transient String SCOPED = "scoped";
public static final transient String ISOLATED = "isolated";
+ private static ZeppelinConfiguration conf = ZeppelinConfiguration.create();
// always set it as true, keep this field just for backward compatibility
boolean remote = true;
@@ -66,6 +68,13 @@ public void setUserPermission(boolean setPermission) {
}
public List getOwners() {
+ if (null != owners && conf.isUsernameForceLowerCase()) {
+ List lowerCaseUsers = new ArrayList();
+ for (String owner : owners) {
+ lowerCaseUsers.add(owner.toLowerCase());
+ }
+ return lowerCaseUsers;
+ }
return owners;
}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java
index f9f5f228ff0..f5329f31683 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/SecurityUtils.java
@@ -44,6 +44,7 @@
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.realm.ActiveDirectoryGroupRealm;
import org.apache.zeppelin.realm.LdapRealm;
+import org.apache.zeppelin.server.ZeppelinServer;
/**
* Tools for securing Zeppelin.
@@ -91,6 +92,11 @@ public static String getPrincipal() {
String principal;
if (subject.isAuthenticated()) {
principal = extractPrincipal(subject);
+ if (ZeppelinServer.notebook.getConf().isUsernameForceLowerCase()) {
+ log.debug("Converting principal name " + principal
+ + " to lower case:" + principal.toLowerCase());
+ principal = principal.toLowerCase();
+ }
} else {
principal = ANONYMOUS;
}
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/security/SecurityUtilsTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/security/SecurityUtilsTest.java
index ac6f1688002..0c535b968eb 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/security/SecurityUtilsTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/security/SecurityUtilsTest.java
@@ -21,23 +21,26 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.InetAddress;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
import org.apache.commons.configuration.ConfigurationException;
+import org.apache.zeppelin.conf.ZeppelinConfiguration;
+import org.apache.zeppelin.notebook.Notebook;
+import org.apache.zeppelin.server.ZeppelinServer;
+import org.apache.zeppelin.utils.SecurityUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
-
-import java.net.InetAddress;
-import java.net.URISyntaxException;
-import java.net.UnknownHostException;
-
import sun.security.acl.PrincipalImpl;
-import org.apache.zeppelin.conf.ZeppelinConfiguration;
-import org.apache.zeppelin.utils.SecurityUtils;
-
@RunWith(PowerMockRunner.class)
@PrepareForTest(org.apache.shiro.SecurityUtils.class)
public class SecurityUtilsTest {
@@ -113,12 +116,49 @@ public void notAURIOrigin()
@Test
public void canGetPrincipalName() {
String expectedName = "java.security.Principal.getName()";
+ setupPrincipalName(expectedName);
+ assertEquals(expectedName, SecurityUtils.getPrincipal());
+ }
+
+ @Test
+ public void testUsernameForceLowerCase() throws IOException, InterruptedException {
+ String expectedName = "java.security.Principal.getName()";
+ System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_USERNAME_FORCE_LOWERCASE
+ .getVarName(), String.valueOf(true));
+ setupPrincipalName(expectedName);
+ assertEquals(expectedName.toLowerCase(), SecurityUtils.getPrincipal());
+
+ }
+
+ private void setupPrincipalName(String expectedName) {
SecurityUtils.setIsEnabled(true);
PowerMockito.mockStatic(org.apache.shiro.SecurityUtils.class);
when(org.apache.shiro.SecurityUtils.getSubject()).thenReturn(subject);
when(subject.isAuthenticated()).thenReturn(true);
when(subject.getPrincipal()).thenReturn(new PrincipalImpl(expectedName));
- assertEquals(expectedName, SecurityUtils.getPrincipal());
+ Notebook notebook = Mockito.mock(Notebook.class);
+ try {
+ setFinalStatic(ZeppelinServer.class.getDeclaredField("notebook"), notebook);
+ when(ZeppelinServer.notebook.getConf())
+ .thenReturn(new ZeppelinConfiguration(this.getClass().getResource("/zeppelin-site.xml")));
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (ConfigurationException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void setFinalStatic(Field field, Object newValue)
+ throws NoSuchFieldException, IllegalAccessException {
+ field.setAccessible(true);
+ Field modifiersField = Field.class.getDeclaredField("modifiers");
+ modifiersField.setAccessible(true);
+ modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+ field.set(null, newValue);
}
+
+
}
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java
index f73b49e3a09..137af651aa9 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java
@@ -205,6 +205,21 @@ public void setWriters(String noteId, Set entities) {
saveToFile();
}
+ /*
+ * If case conversion is enforced, then change entity names to lower case
+ */
+ private Set checkCaseAndConvert(Set entities) {
+ if (conf.isUsernameForceLowerCase()) {
+ Set set2 = new HashSet();
+ for (String name : entities) {
+ set2.add(name.toLowerCase());
+ }
+ return set2;
+ } else {
+ return entities;
+ }
+ }
+
public Set getOwners(String noteId) {
Map> noteAuthInfo = authInfo.get(noteId);
Set entities = null;
@@ -214,6 +229,8 @@ public Set getOwners(String noteId) {
entities = noteAuthInfo.get("owners");
if (entities == null) {
entities = new HashSet<>();
+ } else {
+ entities = checkCaseAndConvert(entities);
}
}
return entities;
@@ -228,6 +245,8 @@ public Set getReaders(String noteId) {
entities = noteAuthInfo.get("readers");
if (entities == null) {
entities = new HashSet<>();
+ } else {
+ entities = checkCaseAndConvert(entities);
}
}
return entities;
@@ -242,6 +261,8 @@ public Set getRunners(String noteId) {
entities = noteAuthInfo.get("runners");
if (entities == null) {
entities = new HashSet<>();
+ } else {
+ entities = checkCaseAndConvert(entities);
}
}
return entities;
@@ -256,6 +277,8 @@ public Set getWriters(String noteId) {
entities = noteAuthInfo.get("writers");
if (entities == null) {
entities = new HashSet<>();
+ } else {
+ entities = checkCaseAndConvert(entities);
}
}
return entities;