Skip to content

Commit

Permalink
[ZEPPELIN-1070] Inject Credentials in any Interpreter-Code
Browse files Browse the repository at this point in the history
  • Loading branch information
Pascal Pellmont authored and jpmcmu committed Jun 25, 2019
1 parent bf2cb0a commit e7060f5
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 7 deletions.
@@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.notebook;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResultMessage;
import org.apache.zeppelin.user.UserCredentials;
import org.apache.zeppelin.user.UsernamePassword;

/**
* Class for replacing $[user.>credentialkey<] and
* $[password.>credentialkey<] tags with the matching credentials from
* zeppelin
*/
class CredentialInjector {

private Set<String> passwords = new HashSet<>();
private final UserCredentials creds;

public CredentialInjector(UserCredentials creds) {
this.creds = creds;
}

public String replaceCredentials(String code) {
if (code == null) {
return null;
}
String replaced = code;
Pattern userpattern = Pattern.compile("\\$\\[user\\.([^\\]]+)\\]");
Pattern passwordpattern = Pattern.compile("\\$\\[password\\.([^\\]]+)\\]");
Matcher matcher = userpattern.matcher(replaced);
while (matcher.find()) {
String key = matcher.group(1);
UsernamePassword usernamePassword = creds.getUsernamePassword(key);
String value = usernamePassword == null ? "undef" : usernamePassword.getUsername();
replaced = matcher.replaceFirst(value);
matcher = userpattern.matcher(replaced);
}
matcher = passwordpattern.matcher(replaced);
while (matcher.find()) {
String key = matcher.group(1);
UsernamePassword usernamePassword = creds.getUsernamePassword(key);
if (usernamePassword != null) {
passwords.add(usernamePassword.getPassword());
}
String value = usernamePassword == null ? "undef" : usernamePassword.getPassword();
replaced = matcher.replaceFirst(value);
matcher = passwordpattern.matcher(replaced);
}
return replaced;
}

public InterpreterResult hidePasswords(InterpreterResult ret) {
if (ret == null) {
return null;
}
return new InterpreterResult(ret.code(), replacePasswords(ret.message()));
}

private List<InterpreterResultMessage> replacePasswords(List<InterpreterResultMessage> original) {
List<InterpreterResultMessage> replaced = new ArrayList<>();
for (InterpreterResultMessage msg : original) {
String replacedMessages = replacePasswords(msg.getData());
replaced.add(new InterpreterResultMessage(msg.getType(), replacedMessages));
}
return replaced;
}

private String replacePasswords(String str) {
String result = str;
for (String password : passwords) {
result = result.replace(password, "###");
}
return result;
}

}
Expand Up @@ -434,7 +434,12 @@ && isUserAuthorizedToAccessInterpreter(interpreterSetting.getOption()) == false)
try {
InterpreterContext context = getInterpreterContext();
InterpreterContext.set(context);
InterpreterResult ret = interpreter.interpret(script, context);
UserCredentials creds = context.getAuthenticationInfo().getUserCredentials();

CredentialInjector credinjector = new CredentialInjector(creds);
String code = credinjector.replaceCredentials(script);
InterpreterResult ret = interpreter.interpret(code, context);
ret = credinjector.hidePasswords(ret);

if (interpreter.getFormType() == FormType.NATIVE) {
note.setNoteParams(context.getNoteGui().getParams());
Expand Down
@@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.zeppelin.notebook;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.user.UserCredentials;
import org.apache.zeppelin.user.UsernamePassword;
import org.junit.Test;

public class CredentialInjectorTest {

private static final String TEMPLATE =
"val jdbcUrl = \"jdbc:mysql://localhost/emp?user=$[user.mysql]&password=$[password.mysql]\"";
private static final String CORRECT_REPLACED =
"val jdbcUrl = \"jdbc:mysql://localhost/emp?user=username&password=pwd\"";
private static final String NOT_REPLACED =
"val jdbcUrl = \"jdbc:mysql://localhost/emp?user=undef&password=undef\"";

private static final String ANSWER =
"jdbcUrl: String = jdbc:mysql://localhost/employees?user=username&password=pwd";
private static final String HIDDEN =
"jdbcUrl: String = jdbc:mysql://localhost/employees?user=username&password=###";

@Test
public void replaceCredentials() {
UserCredentials userCredentials = mock(UserCredentials.class);
UsernamePassword usernamePassword = new UsernamePassword("username", "pwd");
when(userCredentials.getUsernamePassword("mysql")).thenReturn(usernamePassword);
CredentialInjector testee = new CredentialInjector(userCredentials);
String actual = testee.replaceCredentials(TEMPLATE);
assertEquals(CORRECT_REPLACED, actual);

InterpreterResult ret = new InterpreterResult(Code.SUCCESS, ANSWER);
InterpreterResult hiddenResult = testee.hidePasswords(ret);
assertEquals(1, hiddenResult.message().size());
assertEquals(HIDDEN, hiddenResult.message().get(0).getData());
}

@Test
public void replaceCredentialNoTexts() {
UserCredentials userCredentials = mock(UserCredentials.class);
CredentialInjector testee = new CredentialInjector(userCredentials);
String actual = testee.replaceCredentials(null);
assertNull(actual);
}

@Test
public void replaceCredentialsNotExisting() {
UserCredentials userCredentials = mock(UserCredentials.class);
CredentialInjector testee = new CredentialInjector(userCredentials);
String actual = testee.replaceCredentials(TEMPLATE);
assertEquals(NOT_REPLACED, actual);

InterpreterResult ret = new InterpreterResult(Code.SUCCESS, ANSWER);
InterpreterResult hiddenResult = testee.hidePasswords(ret);
assertEquals(1, hiddenResult.message().size());
assertEquals(ANSWER, hiddenResult.message().get(0).getData());
}

@Test
public void hidePasswordsNoResult() {
UserCredentials userCredentials = mock(UserCredentials.class);
CredentialInjector testee = new CredentialInjector(userCredentials);
assertNull(testee.hidePasswords(null));
}

}
Expand Up @@ -23,40 +23,46 @@
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.google.common.collect.Lists;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.tuple.Triple;
import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.AngularObjectBuilder;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.Input;
import org.apache.zeppelin.interpreter.*;
import org.apache.zeppelin.interpreter.AbstractInterpreterTest;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.Interpreter.FormType;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterOption;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.interpreter.InterpreterResult.Type;
import org.apache.zeppelin.interpreter.InterpreterResultMessage;
import org.apache.zeppelin.interpreter.InterpreterSetting;
import org.apache.zeppelin.interpreter.InterpreterSetting.Status;
import org.apache.zeppelin.interpreter.ManagedInterpreterGroup;
import org.apache.zeppelin.resource.ResourcePool;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.apache.zeppelin.user.Credentials;
import org.apache.zeppelin.user.UserCredentials;
import org.apache.zeppelin.user.UsernamePassword;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;
import org.mockito.Mockito;

import com.google.common.collect.Lists;

public class ParagraphTest extends AbstractInterpreterTest {

@Test
Expand Down Expand Up @@ -299,4 +305,42 @@ public void testCursorPosition() {
}
}

@Test
public void credentialReplacement() throws Throwable {
Note mockNote = mock(Note.class);
Credentials creds = mock(Credentials.class);
when(mockNote.getCredentials()).thenReturn(creds);
Paragraph spyParagraph = spy(new Paragraph("para_1", mockNote, null, null));
UserCredentials uc = mock(UserCredentials.class);
when(creds.getUserCredentials(anyString())).thenReturn(uc);
UsernamePassword up = new UsernamePassword("user", "pwd");
when(uc.getUsernamePassword("ent")).thenReturn(up );

Interpreter mockInterpreter = mock(Interpreter.class);
spyParagraph.setInterpreter(mockInterpreter);
doReturn(mockInterpreter).when(spyParagraph).getBindedInterpreter();

ManagedInterpreterGroup mockInterpreterGroup = mock(ManagedInterpreterGroup.class);
when(mockInterpreter.getInterpreterGroup()).thenReturn(mockInterpreterGroup);
when(mockInterpreterGroup.getId()).thenReturn("mock_id_1");
when(mockInterpreterGroup.getAngularObjectRegistry()).thenReturn(mock(AngularObjectRegistry.class));
when(mockInterpreterGroup.getResourcePool()).thenReturn(mock(ResourcePool.class));
when(mockInterpreter.getFormType()).thenReturn(FormType.NONE);

ParagraphJobListener mockJobListener = mock(ParagraphJobListener.class);
doReturn(mockJobListener).when(spyParagraph).getListener();
doNothing().when(mockJobListener).onOutputUpdateAll(Mockito.<Paragraph>any(), Mockito.anyList());

InterpreterResult mockInterpreterResult = mock(InterpreterResult.class);
when(mockInterpreter.interpret(anyString(), Mockito.<InterpreterContext>any())).thenReturn(mockInterpreterResult);
when(mockInterpreterResult.code()).thenReturn(Code.SUCCESS);

AuthenticationInfo user1 = new AuthenticationInfo("user1");
spyParagraph.setAuthenticationInfo(user1);

spyParagraph.setText("val x = \"usr=$[user.ent]&pass=$[password.ent]\"");
spyParagraph.jobRun();

verify(mockInterpreter).interpret(eq("val x = \"usr=user&pass=pwd\""), any(InterpreterContext.class));
}
}

0 comments on commit e7060f5

Please sign in to comment.