-
Notifications
You must be signed in to change notification settings - Fork 2.8k
[Zeppelin-945] Interpreter authorization #1257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
407d260
29d8f43
b34fdf7
1ae1c6a
635d523
e56e5b1
e1679b3
e72c097
e1e7a35
e42cb9e
febce0c
f07542a
3e25159
7ed8ad6
2c48ded
83097ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,8 @@ | |
|
|
||
| package org.apache.zeppelin.interpreter; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| /** | ||
| * | ||
| */ | ||
|
|
@@ -28,7 +30,8 @@ public class InterpreterOption { | |
| boolean perNoteProcess; | ||
|
|
||
| boolean isExistingProcess; | ||
|
|
||
| boolean setPermission; | ||
| List<String> users; | ||
|
|
||
| public boolean isExistingProcess() { | ||
| return isExistingProcess; | ||
|
|
@@ -46,6 +49,17 @@ public void setHost(String host) { | |
| this.host = host; | ||
| } | ||
|
|
||
| public boolean isSetPermission() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is quite an ambiguous naming - usually in Java if (permissionIsSet()) {
//....
} |
||
| return setPermission; | ||
| } | ||
|
|
||
| public void setUserPermission(boolean setPermission) { | ||
| this.setPermission = setPermission; | ||
| } | ||
|
|
||
| public List<String> getUsers() { | ||
| return users; | ||
| } | ||
|
|
||
| public InterpreterOption() { | ||
| remote = false; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -275,6 +275,19 @@ public Map<String, Object> info() { | |
| return null; | ||
| } | ||
|
|
||
| private boolean hasPermission(String user, List<String> intpUsers) { | ||
| if (1 > intpUsers.size()) { | ||
| return true; | ||
| } | ||
|
|
||
| for (String u: intpUsers) { | ||
| if (user.trim().equals(u.trim())) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| protected Object jobRun() throws Throwable { | ||
| String replName = getRequiredReplName(); | ||
|
|
@@ -285,6 +298,24 @@ protected Object jobRun() throws Throwable { | |
| throw new RuntimeException("Can not find interpreter for " + getRequiredReplName()); | ||
| } | ||
|
|
||
| if (this.user != null && | ||
| !factory.getInterpreterSettings(note.getId()).isEmpty()) { | ||
| for (InterpreterSetting intp: factory.getInterpreterSettings(note.getId())){ | ||
|
|
||
| if (replName.startsWith(intp.getName()) && | ||
| intp.getOption().isSetPermission() && | ||
| !hasPermission(authenticationInfo.getUser(), intp.getOption().getUsers())) { | ||
| logger.error("{} has no permission for {} ", authenticationInfo.getUser(), repl); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just want to give you a perspective of somebody who reads this code: it is VERY hard to understand and reason what happens in both conditions here. By looking at first condition: this.user != null && !factory.getInterpreterSettings(note.getId()).isEmpty()I would assume that is some kind of pre-condition check, because we do not do anything in case it is Then I would look at both conditions to understand, what they mean, starting from Same for And all this questions for reader could be avoided, and time saved, if we give a reader a clue in form of readable names for this conditions in english - we can extract this logic to the functions that self-describe it's purpose through the function names (you can think of it as a form of documentation): if (this.noteHasUser() && this.noteHasInterpreters()) {
//...
}
boolean noteHasUser() {
return this.user != null;
}
boolean noteHasInterpreters() {
// BTW, this is used in 2 other places and looks like candidate for further simplification
// return !getInterpreterListFor(this.note).isEmpty();
return !factory.getInterpreyterSettings(note.getId()).isEmpty();
}By looking at second condition: replName.startsWith(intp.getName()) &&
+ intp.getOption().isSetPermission() &&
+ !hasPermission(authenticationInfo.getUser(), intp.getOption().getUsers())as a reader, for me it's even harder to say, what case does this represent. Judging by the loop and an error message - it looks this code does to things:
So a function can be extracted here as well: boolean isUserAuthorizedToAccessInterpreter(String user, InterpreterOption intpOpt)){
return intpOpt.isSetPermission() &&
!hasPermission(authenticationInfo.getUser(), intpOpt.getUsers())
}But it can be even further simplified if we decouple logic for finding current InterpreterSettings findInerpreterByName(String name) {
InterpreterSettings intp = null; // this would rather be `Option()` or `InterpreterSettings.NONE` or anything else type-safe
for (InterpreterSetting i: factory.getInterpreterSettings(note.getId())) { // could re-use `getInterpreterListFor(this.note)` from above
if (i.getName().startsWith(name)) {
interp = i;
}
}
return intp;
}So, final results Before if (this.user != null &&
!factory.getInterpreterSettings(note.getId()).isEmpty()) {
for (InterpreterSetting intp: factory.getInterpreterSettings(note.getId())){
if (replName.startsWith(intp.getName()) &&
intp.getOption().isSetPermission() &&
!hasPermission(authenticationInfo.getUser(), intp.getOption().getUsers())) {
return new InterpreterResult(...);
}
}
}After if (this.noteHasUser() && this.noteHasInterpreters()) {
InterpreterSetting intp = findInerpreterByName(replName);
if (intp != null &&
isUserAuthorizedToAccessInterpreter(authenticationInfo.getUser(), intp.getOption())) {
return new InterpreterResult(...);
}
}I'm not native english speaker, but the resulting code very much looks like an english sentence. It explains the logic for the reader, who can follow the implementation details by looking at the new functions. What do you think, does this make sense?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All your comments are totally makes sense and thank you very much for the detail review to spend your precious time. |
||
| return new InterpreterResult(Code.ERROR, authenticationInfo.getUser() + | ||
| " has no permission for " + getRequiredReplName()); | ||
| /* | ||
| throw new RuntimeException(authenticationInfo.getUser() + | ||
| " has no permission for " + getRequiredReplName()); | ||
| */ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is not required can you remove it.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I deleted it. Thanks. |
||
| } | ||
| } | ||
| } | ||
|
|
||
| String script = getScriptBody(); | ||
| // inject form | ||
| if (repl.getFormType() == FormType.NATIVE) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you move this directive to a different file
zeppelin-web/src/components/*There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the directive. Thanks!