Repository: zeppelin Updated Branches: refs/heads/master 794c66b08 -> 32fe23f2e
[ZEPPELIN-1586] Add security check in NotebookRestApi ### What is this PR for? Bring some security check in `NotebookRestApi`. ### What type of PR is it? [Bug Fix | Improvement | Refactoring] ### Todos - [x] - Create a proper way to throw webapp error - [x] - Add in `NotebookAuthorization` some method to check if user is owner, reader or writer - [x] - Add Authorization check in `NotebookRestapi` - [x] - Add New test for security in notebook rest api ### What is the Jira issue? - [ZEPPELIN-1586](https://issues.apache.org/jira/browse/ZEPPELIN-1586) ### How should this be tested? First, force Zeppelin to use auth. - In `conf/zeppelin-site.xml` change `zeppelin.anonymous.allowed` to **false** ``` <property> <name>zeppelin.anonymous.allowed</name> <value>false</value> <description>Anonymous user allowed by default</description> </property> ``` - In `conf/shiro.ini` set Shiro to use `Auth` at the end of the file ``` #/** = anon /** = authc ``` - Start Zeppelin, login and set some permission to a note - try to get a note from Zeppelin Rest Api `http://localhost:8080/api/notebook/{noteId}` (you can use your browser or curl (if you use curl please add shiro token to curl cookie)) ### Screenshots (if appropriate)  ### Questions: - Does the licenses files need update? No - Is there breaking changes for older versions? No - Does this needs documentation? Maybe Author: Anthony Corbacho <[email protected]> Closes #1567 from anthonycorbacho/fix/ZEPPELIN-1586 and squashes the following commits: 6615935 [Anthony Corbacho] Clean anonymous allowed property when shutting down zeppelin server 30815c1 [Anthony Corbacho] Fix typo bab7e60 [Anthony Corbacho] Rewording decd1e9 [Anthony Corbacho] Simple implementation of notebook test with shiro (security) b412266 [Anthony Corbacho] Refactored Abstract rest api test to also handle the case of tests with shiro (security), I also added some utility http method to do action with authenticated user db0c39c [Anthony Corbacho] Adress review and fix typos eacfa8e [Anthony Corbacho] Fix typo and bad copy paste for isOwner c8c42b2 [Anthony Corbacho] Change cxf version from 2.7.7 to 2.7.8 to avoid method not found where throw WebAppException ed404a4 [Anthony Corbacho] Rename permission check note :: be more meaningful 6030776 [Anthony Corbacho] Handle security check fe380ab [Anthony Corbacho] Add webapp exception handler :) 21f9288 [Anthony Corbacho] Replace check of aninonimous by method 0e4cc3c [Anthony Corbacho] Add new method to check if user and roles are member of the note (at least owner, reader, writer) da3415f [Anthony Corbacho] Add new method to help to determinate if user is part of writer and/or owner for the given note 4a43b07 [Anthony Corbacho] Add new method on ZeppelinConfiguration to get is zeppelin is running on anonimous mode or not Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/32fe23f2 Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/32fe23f2 Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/32fe23f2 Branch: refs/heads/master Commit: 32fe23f2e283f6e555f54861ff20376507d2b705 Parents: 794c66b Author: Anthony Corbacho <[email protected]> Authored: Thu Nov 3 11:59:07 2016 +0900 Committer: Mina Lee <[email protected]> Committed: Thu Nov 3 15:16:14 2016 +0900 ---------------------------------------------------------------------- zeppelin-server/pom.xml | 2 +- .../apache/zeppelin/rest/NotebookRestApi.java | 217 +++++++++++-------- .../rest/exception/NotFoundException.java | 59 +++++ .../rest/exception/UnauthorizedException.java | 50 +++++ .../apache/zeppelin/socket/NotebookServer.java | 42 ++-- .../apache/zeppelin/utils/ExceptionUtils.java | 36 +++ .../zeppelin/rest/AbstractTestRestApi.java | 115 +++++++++- .../rest/NotebookSecurityRestApiTest.java | 156 +++++++++++++ .../zeppelin/conf/ZeppelinConfiguration.java | 6 +- .../notebook/NotebookAuthorization.java | 60 ++++- 10 files changed, 623 insertions(+), 120 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-server/pom.xml ---------------------------------------------------------------------- diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml index 862fc30..ee53a37 100644 --- a/zeppelin-server/pom.xml +++ b/zeppelin-server/pom.xml @@ -33,7 +33,7 @@ <name>Zeppelin: Server</name> <properties> - <cxf.version>2.7.7</cxf.version> + <cxf.version>2.7.8</cxf.version> <commons.httpclient.version>4.3.6</commons.httpclient.version> <hadoop-common.version>2.6.0</hadoop-common.version> </properties> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java index ae66330..5b27d0e 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; + import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -34,31 +35,33 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import com.google.common.collect.Sets; -import com.google.common.reflect.TypeToken; -import com.google.gson.Gson; import org.apache.commons.lang3.StringUtils; -import org.apache.zeppelin.interpreter.InterpreterResult; -import org.apache.zeppelin.utils.InterpreterBindingUtils; -import org.quartz.CronExpression; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import org.apache.zeppelin.annotation.ZeppelinApi; +import org.apache.zeppelin.interpreter.InterpreterResult; import org.apache.zeppelin.notebook.Note; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.NotebookAuthorization; import org.apache.zeppelin.notebook.Paragraph; +import org.apache.zeppelin.rest.exception.NotFoundException; +import org.apache.zeppelin.rest.exception.UnauthorizedException; import org.apache.zeppelin.rest.message.CronRequest; -import org.apache.zeppelin.types.InterpreterSettingsList; import org.apache.zeppelin.rest.message.NewNoteRequest; import org.apache.zeppelin.rest.message.NewParagraphRequest; import org.apache.zeppelin.rest.message.RunParagraphWithParametersRequest; import org.apache.zeppelin.search.SearchService; import org.apache.zeppelin.server.JsonResponse; import org.apache.zeppelin.socket.NotebookServer; +import org.apache.zeppelin.types.InterpreterSettingsList; import org.apache.zeppelin.user.AuthenticationInfo; +import org.apache.zeppelin.utils.InterpreterBindingUtils; import org.apache.zeppelin.utils.SecurityUtils; +import org.quartz.CronExpression; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; /** * Rest api endpoint for the notebook. @@ -90,6 +93,8 @@ public class NotebookRestApi { @Path("{noteId}/permissions") @ZeppelinApi public Response getNotePermissions(@PathParam("noteId") String noteId) { + checkIfUserCanRead(noteId, + "Insufficient privileges you cannot get the list of permissions for this note"); HashMap<String, Set<String>> permissionsMap = new HashMap<>(); permissionsMap.put("owners", notebookAuthorization.getOwners(noteId)); permissionsMap.put("readers", notebookAuthorization.getReaders(noteId)); @@ -106,6 +111,60 @@ public class NotebookRestApi { } /** + * Set of utils method to check if current user can perform action to the note. + * Since we only have security on notebook level, from now we keep this logic in this class. + * In the future we might want to generalize this for the rest of the api enmdpoints. + */ + + /** + * Check if the current user own the given note. + */ + private void checkIfUserIsOwner(String noteId, String errorMsg) { + Set<String> userAndRoles = Sets.newHashSet(); + userAndRoles.add(SecurityUtils.getPrincipal()); + userAndRoles.addAll(SecurityUtils.getRoles()); + if (!notebookAuthorization.isOwner(userAndRoles, noteId)) { + throw new UnauthorizedException(errorMsg); + } + } + + /** + * Check if the current user is either Owner or Writer for the given note. + */ + private void checkIfUserCanWrite(String noteId, String errorMsg) { + Set<String> userAndRoles = Sets.newHashSet(); + userAndRoles.add(SecurityUtils.getPrincipal()); + userAndRoles.addAll(SecurityUtils.getRoles()); + if (!notebookAuthorization.hasWriteAuthorization(userAndRoles, noteId)) { + throw new UnauthorizedException(errorMsg); + } + } + + /** + * Check if the current user can access (at least he have to be reader) the given note. + */ + private void checkIfUserCanRead(String noteId, String errorMsg) { + Set<String> userAndRoles = Sets.newHashSet(); + userAndRoles.add(SecurityUtils.getPrincipal()); + userAndRoles.addAll(SecurityUtils.getRoles()); + if (!notebookAuthorization.hasReadAuthorization(userAndRoles, noteId)) { + throw new UnauthorizedException(errorMsg); + } + } + + private void checkIfNoteIsNotNull(Note note) { + if (note == null) { + throw new NotFoundException("note not found"); + } + } + + private void checkIfParagraphIsNotNull(Paragraph paragraph) { + if (paragraph == null) { + throw new NotFoundException("paragraph not found"); + } + } + + /** * set note authorization information */ @PUT @@ -113,22 +172,21 @@ public class NotebookRestApi { @ZeppelinApi public Response putNotePermissions(@PathParam("noteId") String noteId, String req) throws IOException { - HashMap<String, HashSet<String>> permMap = - gson.fromJson(req, new TypeToken<HashMap<String, HashSet<String>>>() { - }.getType()); - Note note = notebook.getNote(noteId); String principal = SecurityUtils.getPrincipal(); HashSet<String> roles = SecurityUtils.getRoles(); - LOG.info("Set permissions {} {} {} {} {}", noteId, principal, permMap.get("owners"), - permMap.get("readers"), permMap.get("writers")); - HashSet<String> userAndRoles = new HashSet<>(); userAndRoles.add(principal); userAndRoles.addAll(roles); - if (!notebookAuthorization.isOwner(noteId, userAndRoles)) { - return new JsonResponse<>(Status.FORBIDDEN, - ownerPermissionError(userAndRoles, notebookAuthorization.getOwners(noteId))).build(); - } + + checkIfUserIsOwner(noteId, + ownerPermissionError(userAndRoles, notebookAuthorization.getOwners(noteId))); + + HashMap<String, HashSet<String>> permMap = + gson.fromJson(req, new TypeToken<HashMap<String, HashSet<String>>>() {}.getType()); + Note note = notebook.getNote(noteId); + + LOG.info("Set permissions {} {} {} {} {}", noteId, principal, permMap.get("owners"), + permMap.get("readers"), permMap.get("writers")); HashSet<String> readers = permMap.get("readers"); HashSet<String> owners = permMap.get("owners"); @@ -170,6 +228,9 @@ public class NotebookRestApi { @Path("interpreter/bind/{noteId}") @ZeppelinApi public Response bind(@PathParam("noteId") String noteId, String req) throws IOException { + checkIfUserCanWrite(noteId, + "Insufficient privileges you cannot bind any interpreters to this note"); + List<String> settingIdList = gson.fromJson(req, new TypeToken<List<String>>() { }.getType()); notebook.bindInterpretersToNote(SecurityUtils.getPrincipal(), noteId, settingIdList); @@ -183,6 +244,8 @@ public class NotebookRestApi { @Path("interpreter/bind/{noteId}") @ZeppelinApi public Response bind(@PathParam("noteId") String noteId) { + checkIfUserCanRead(noteId, "Insufficient privileges you cannot get any interpreters settings"); + List<InterpreterSettingsList> settingList = InterpreterBindingUtils.getInterpreterBindings(notebook, noteId); notebookServer.broadcastInterpreterBindings(noteId, settingList); @@ -204,9 +267,8 @@ public class NotebookRestApi { @ZeppelinApi public Response getNote(@PathParam("noteId") String noteId) throws IOException { Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanRead(noteId, "Insufficient privileges you cannot get this note"); return new JsonResponse<>(Status.OK, "", note).build(); } @@ -222,6 +284,7 @@ public class NotebookRestApi { @Path("export/{noteId}") @ZeppelinApi public Response exportNote(@PathParam("noteId") String noteId) throws IOException { + checkIfUserCanRead(noteId, "Insufficient privileges you cannot export this note"); String exportJson = notebook.exportNote(noteId); return new JsonResponse<>(Status.OK, "", exportJson).build(); } @@ -290,6 +353,7 @@ public class NotebookRestApi { @ZeppelinApi public Response deleteNote(@PathParam("noteId") String noteId) throws IOException { LOG.info("Delete note {} ", noteId); + checkIfUserIsOwner(noteId, "Insufficient privileges you cannot delete this note"); AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); if (!(noteId.isEmpty())) { Note note = notebook.getNote(noteId); @@ -315,6 +379,7 @@ public class NotebookRestApi { public Response cloneNote(@PathParam("noteId") String noteId, String message) throws IOException, CloneNotSupportedException, IllegalArgumentException { LOG.info("clone note by JSON {}", message); + checkIfUserCanWrite(noteId, "Insufficient privileges you cannot clone this note"); NewNoteRequest request = gson.fromJson(message, NewNoteRequest.class); String newNoteName = null; if (request != null) { @@ -342,9 +407,8 @@ public class NotebookRestApi { LOG.info("insert paragraph {} {}", noteId, message); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanWrite(noteId, "Insufficient privileges you cannot add paragraph to this note"); NewParagraphRequest request = gson.fromJson(message, NewParagraphRequest.class); @@ -379,14 +443,10 @@ public class NotebookRestApi { LOG.info("get paragraph {} {}", noteId, paragraphId); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); - } - + checkIfNoteIsNotNull(note); + checkIfUserCanRead(noteId, "Insufficient privileges you cannot get this paragraph"); Paragraph p = note.getParagraph(paragraphId); - if (p == null) { - return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build(); - } + checkIfParagraphIsNotNull(p); return new JsonResponse<>(Status.OK, "", p).build(); } @@ -407,14 +467,11 @@ public class NotebookRestApi { LOG.info("move paragraph {} {} {}", noteId, paragraphId, newIndex); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanWrite(noteId, "Insufficient privileges you cannot move paragraph"); Paragraph p = note.getParagraph(paragraphId); - if (p == null) { - return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build(); - } + checkIfParagraphIsNotNull(p); try { note.moveParagraph(paragraphId, Integer.parseInt(newIndex), true); @@ -444,14 +501,12 @@ public class NotebookRestApi { LOG.info("delete paragraph {} {}", noteId, paragraphId); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanRead(noteId, + "Insufficient privileges you cannot remove paragraph from this note"); Paragraph p = note.getParagraph(paragraphId); - if (p == null) { - return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build(); - } + checkIfParagraphIsNotNull(p); AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal()); note.removeParagraph(SecurityUtils.getPrincipal(), paragraphId); @@ -475,9 +530,8 @@ public class NotebookRestApi { throws IOException, IllegalArgumentException { LOG.info("run note jobs {} ", noteId); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanWrite(noteId, "Insufficient privileges you cannot run job for this note"); try { note.runAll(); @@ -504,9 +558,8 @@ public class NotebookRestApi { throws IOException, IllegalArgumentException { LOG.info("stop note jobs {} ", noteId); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanWrite(noteId, "Insufficient privileges you cannot stop this job for this note"); for (Paragraph p : note.getParagraphs()) { if (!p.isTerminated()) { @@ -530,9 +583,8 @@ public class NotebookRestApi { throws IOException, IllegalArgumentException { LOG.info("get note job status."); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanRead(noteId, "Insufficient privileges you cannot get job status"); return new JsonResponse<>(Status.OK, null, note.generateParagraphsInfo()).build(); } @@ -553,14 +605,11 @@ public class NotebookRestApi { throws IOException, IllegalArgumentException { LOG.info("get note paragraph job status."); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanRead(noteId, "Insufficient privileges you cannot get job status"); Paragraph paragraph = note.getParagraph(paragraphId); - if (paragraph == null) { - return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build(); - } + checkIfParagraphIsNotNull(paragraph); return new JsonResponse<>(Status.OK, null, note.generateSingleParagraphInfo(paragraphId)). build(); @@ -583,14 +632,10 @@ public class NotebookRestApi { LOG.info("run paragraph job asynchronously {} {} {}", noteId, paragraphId, message); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } - + checkIfNoteIsNotNull(note); + checkIfUserCanWrite(noteId, "Insufficient privileges you cannot run job for this note"); Paragraph paragraph = note.getParagraph(paragraphId); - if (paragraph == null) { - return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build(); - } + checkIfParagraphIsNotNull(paragraph); // handle params if presented handleParagraphParams(message, note, paragraph); @@ -625,14 +670,10 @@ public class NotebookRestApi { LOG.info("run paragraph synchronously {} {} {}", noteId, paragraphId, message); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } - + checkIfNoteIsNotNull(note); + checkIfUserCanWrite(noteId, "Insufficient privileges you cannot run paragraph"); Paragraph paragraph = note.getParagraph(paragraphId); - if (paragraph == null) { - return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build(); - } + checkIfParagraphIsNotNull(paragraph); // handle params if presented handleParagraphParams(message, note, paragraph); @@ -667,14 +708,10 @@ public class NotebookRestApi { @PathParam("paragraphId") String paragraphId) throws IOException, IllegalArgumentException { LOG.info("stop paragraph job {} ", noteId); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } - + checkIfNoteIsNotNull(note); + checkIfUserCanWrite(noteId, "Insufficient privileges you cannot stop paragraph"); Paragraph p = note.getParagraph(paragraphId); - if (p == null) { - return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build(); - } + checkIfParagraphIsNotNull(p); p.abort(); return new JsonResponse<>(Status.OK).build(); } @@ -696,9 +733,8 @@ public class NotebookRestApi { CronRequest request = gson.fromJson(message, CronRequest.class); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanWrite(noteId, "Insufficient privileges you cannot set a cron job for this note"); if (!CronExpression.isValidExpression(request.getCronString())) { return new JsonResponse<>(Status.BAD_REQUEST, "wrong cron expressions.").build(); @@ -727,9 +763,9 @@ public class NotebookRestApi { LOG.info("Remove cron job note {}", noteId); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserIsOwner(noteId, + "Insufficient privileges you cannot remove this cron job from this note"); Map<String, Object> config = note.getConfig(); config.put("cron", null); @@ -754,9 +790,8 @@ public class NotebookRestApi { LOG.info("Get cron job note {}", noteId); Note note = notebook.getNote(noteId); - if (note == null) { - return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build(); - } + checkIfNoteIsNotNull(note); + checkIfUserCanRead(noteId, "Insufficient privileges you cannot get cron information"); return new JsonResponse<>(Status.OK, note.getConfig().get("cron")).build(); } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/NotFoundException.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/NotFoundException.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/NotFoundException.java new file mode 100644 index 0000000..7f9c17d --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/NotFoundException.java @@ -0,0 +1,59 @@ +/* + * 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.rest.exception; +import static javax.ws.rs.core.Response.Status.NOT_FOUND; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; + +import org.apache.zeppelin.utils.ExceptionUtils; + +/** + * Not Found handler for WebApplicationException. + * + */ +public class NotFoundException extends WebApplicationException { + private static final long serialVersionUID = 2459398393216512293L; + + /** + * Create a HTTP 404 (Not Found) exception. + */ + public NotFoundException() { + super(ExceptionUtils.jsonResponse(NOT_FOUND)); + } + + /** + * Create a HTTP 404 (Not Found) exception. + * @param message the String that is the entity of the 404 response. + */ + public NotFoundException(String message) { + super(notFoundJson(message)); + } + + private static Response notFoundJson(String message) { + return ExceptionUtils.jsonResponseContent(NOT_FOUND, message); + } + + public NotFoundException(Throwable cause) { + super(cause, notFoundJson(cause.getMessage())); + } + + public NotFoundException(Throwable cause, String message) { + super(cause, notFoundJson(message)); + } + +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java new file mode 100644 index 0000000..7b968ab --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/exception/UnauthorizedException.java @@ -0,0 +1,50 @@ +/* + * 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.rest.exception; + +import static javax.ws.rs.core.Response.Status.FORBIDDEN; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; + +import org.apache.zeppelin.utils.ExceptionUtils; + +/** + * UnauthorizedException handler for WebApplicationException. + * + */ +public class UnauthorizedException extends WebApplicationException { + private static final long serialVersionUID = 4394749068760407567L; + private static final String UNAUTHORIZED_MSG = "Authorization required"; + + public UnauthorizedException() { + super(unauthorizedJson(UNAUTHORIZED_MSG)); + } + + private static Response unauthorizedJson(String message) { + return ExceptionUtils.jsonResponseContent(FORBIDDEN, message); + } + + public UnauthorizedException(Throwable cause, String message) { + super(cause, unauthorizedJson(message)); + } + + public UnauthorizedException(String message) { + super(unauthorizedJson(message)); + } + +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java index 28a9ac3..4934265 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java @@ -16,10 +16,22 @@ */ package org.apache.zeppelin.socket; -import com.google.common.base.Strings; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.lang.StringUtils; import org.apache.commons.vfs2.FileSystemException; import org.apache.zeppelin.conf.ZeppelinConfiguration; @@ -36,7 +48,13 @@ import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry; import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcessListener; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; -import org.apache.zeppelin.notebook.*; +import org.apache.zeppelin.notebook.JobListenerFactory; +import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.notebook.Notebook; +import org.apache.zeppelin.notebook.NotebookAuthorization; +import org.apache.zeppelin.notebook.NotebookEventListener; +import org.apache.zeppelin.notebook.Paragraph; +import org.apache.zeppelin.notebook.ParagraphJobListener; import org.apache.zeppelin.notebook.repo.NotebookRepo.Revision; import org.apache.zeppelin.notebook.socket.Message; import org.apache.zeppelin.notebook.socket.Message.OP; @@ -54,13 +72,10 @@ import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; /** * Zeppelin websocket service. @@ -147,8 +162,7 @@ public class NotebookServer extends WebSocketServlet implements } ZeppelinConfiguration conf = ZeppelinConfiguration.create(); - boolean allowAnonymous = conf. - getBoolean(ZeppelinConfiguration.ConfVars.ZEPPELIN_ANONYMOUS_ALLOWED); + boolean allowAnonymous = conf.isAnonymousAllowed(); if (!allowAnonymous && messagereceived.principal.equals("anonymous")) { throw new Exception("Anonymous access not allowed "); } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-server/src/main/java/org/apache/zeppelin/utils/ExceptionUtils.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/utils/ExceptionUtils.java b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/ExceptionUtils.java new file mode 100644 index 0000000..ce87e5e --- /dev/null +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/utils/ExceptionUtils.java @@ -0,0 +1,36 @@ +/* + * 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.utils; + +import javax.ws.rs.core.Response.Status; + +import org.apache.zeppelin.server.JsonResponse; + +/** + * Utility method for exception in rest api. + * + */ +public class ExceptionUtils { + + public static javax.ws.rs.core.Response jsonResponse(Status status) { + return new JsonResponse<>(status).build(); + } + + public static javax.ws.rs.core.Response jsonResponseContent(Status status, String message) { + return new JsonResponse<>(status, message).build(); + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java index 823b1dd..6d10337 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java @@ -26,18 +26,22 @@ import java.util.List; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.regex.Pattern; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.httpclient.methods.RequestEntity; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.server.ZeppelinServer; @@ -47,6 +51,7 @@ import org.hamcrest.TypeSafeMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; @@ -60,6 +65,29 @@ public abstract class AbstractTestRestApi { protected static final boolean wasRunning = checkIfServerIsRunning(); static boolean pySpark = false; static boolean sparkR = false; + static Gson gson = new Gson(); + static boolean isRunningWithAuth = false; + + private static File shiroIni = null; + private static String zeppelinShiro = + "[users]\n" + + "admin = password1, admin\n" + + "user1 = password2, role1, role2\n" + + "user2 = password3, role3\n" + + "user3 = password4, role2\n" + + "[main]\n" + + "sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager\n" + + "securityManager.sessionManager = $sessionManager\n" + + "securityManager.sessionManager.globalSessionTimeout = 86400000\n" + + "shiro.loginUrl = /api/login\n" + + "[roles]\n" + + "role1 = *\n" + + "role2 = *\n" + + "role3 = *\n" + + "admin = *" + + "[urls]\n" + + "/api/version = anon\n" + + "/** = authc"; private String getUrl(String path) { String url; @@ -95,15 +123,27 @@ public abstract class AbstractTestRestApi { } }; - protected static void startUp() throws Exception { + private static void start(boolean withAuth) throws Exception { if (!wasRunning) { System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_HOME.getVarName(), "../"); System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_WAR.getVarName(), "../zeppelin-web/dist"); LOG.info("Staring test Zeppelin up..."); + ZeppelinConfiguration conf = ZeppelinConfiguration.create(); + if (withAuth) { + isRunningWithAuth = true; + // Set Anonymous session to false. + System.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_ANONYMOUS_ALLOWED.getVarName(), "false"); + + // Create a shiro env test. + shiroIni = new File("../conf/shiro.ini"); + if (!shiroIni.exists()) { + shiroIni.createNewFile(); + } + FileUtils.writeStringToFile(shiroIni, zeppelinShiro); + } // exclude org.apache.zeppelin.rinterpreter.* for scala 2.11 test - ZeppelinConfiguration conf = ZeppelinConfiguration.create(); String interpreters = conf.getString(ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETERS); String interpretersCompatibleWithScala211Test = null; @@ -184,6 +224,14 @@ public abstract class AbstractTestRestApi { } } } + + protected static void startUpWithAuthenticationEnable() throws Exception { + start(true); + } + + protected static void startUp() throws Exception { + start(false); + } private static String getHostname() { try { @@ -244,7 +292,9 @@ public abstract class AbstractTestRestApi { for (String setting : settingList) { ZeppelinServer.notebook.getInterpreterFactory().restart(setting); } - + if (shiroIni != null) { + FileUtils.deleteQuietly(shiroIni); + } LOG.info("Terminating test Zeppelin..."); ZeppelinServer.jettyWebServer.stop(); executor.shutdown(); @@ -265,6 +315,11 @@ public abstract class AbstractTestRestApi { LOG.info("Test Zeppelin terminated."); System.clearProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETERS.getVarName()); + if (isRunningWithAuth) { + isRunningWithAuth = false; + System + .clearProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_ANONYMOUS_ALLOWED.getVarName()); + } } } @@ -286,49 +341,97 @@ public abstract class AbstractTestRestApi { } protected static GetMethod httpGet(String path) throws IOException { + return httpGet(path, StringUtils.EMPTY, StringUtils.EMPTY); + } + + protected static GetMethod httpGet(String path, String user, String pwd) throws IOException { LOG.info("Connecting to {}", url + path); HttpClient httpClient = new HttpClient(); GetMethod getMethod = new GetMethod(url + path); getMethod.addRequestHeader("Origin", url); + if (userAndPasswordAreNotBlank(user, pwd)) { + getMethod.setRequestHeader("Cookie", "JSESSIONID="+ getCookie(user, pwd)); + } httpClient.executeMethod(getMethod); LOG.info("{} - {}", getMethod.getStatusCode(), getMethod.getStatusText()); return getMethod; } protected static DeleteMethod httpDelete(String path) throws IOException { + return httpDelete(path, StringUtils.EMPTY, StringUtils.EMPTY); + } + + protected static DeleteMethod httpDelete(String path, String user, String pwd) throws IOException { LOG.info("Connecting to {}", url + path); HttpClient httpClient = new HttpClient(); DeleteMethod deleteMethod = new DeleteMethod(url + path); deleteMethod.addRequestHeader("Origin", url); + if (userAndPasswordAreNotBlank(user, pwd)) { + deleteMethod.setRequestHeader("Cookie", "JSESSIONID="+ getCookie(user, pwd)); + } httpClient.executeMethod(deleteMethod); LOG.info("{} - {}", deleteMethod.getStatusCode(), deleteMethod.getStatusText()); return deleteMethod; } protected static PostMethod httpPost(String path, String body) throws IOException { + return httpPost(path, body, StringUtils.EMPTY, StringUtils.EMPTY); + } + + protected static PostMethod httpPost(String path, String request, String user, String pwd) + throws IOException { LOG.info("Connecting to {}", url + path); HttpClient httpClient = new HttpClient(); PostMethod postMethod = new PostMethod(url + path); - postMethod.addRequestHeader("Origin", url); - RequestEntity entity = new ByteArrayRequestEntity(body.getBytes("UTF-8")); - postMethod.setRequestEntity(entity); + postMethod.setRequestBody(request); + postMethod.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES); + if (userAndPasswordAreNotBlank(user, pwd)) { + postMethod.setRequestHeader("Cookie", "JSESSIONID="+ getCookie(user, pwd)); + } httpClient.executeMethod(postMethod); LOG.info("{} - {}", postMethod.getStatusCode(), postMethod.getStatusText()); return postMethod; } protected static PutMethod httpPut(String path, String body) throws IOException { + return httpPut(path, body, StringUtils.EMPTY, StringUtils.EMPTY); + } + + protected static PutMethod httpPut(String path, String body, String user, String pwd) throws IOException { LOG.info("Connecting to {}", url + path); HttpClient httpClient = new HttpClient(); PutMethod putMethod = new PutMethod(url + path); putMethod.addRequestHeader("Origin", url); RequestEntity entity = new ByteArrayRequestEntity(body.getBytes("UTF-8")); putMethod.setRequestEntity(entity); + if (userAndPasswordAreNotBlank(user, pwd)) { + putMethod.setRequestHeader("Cookie", "JSESSIONID="+ getCookie(user, pwd)); + } httpClient.executeMethod(putMethod); LOG.info("{} - {}", putMethod.getStatusCode(), putMethod.getStatusText()); return putMethod; } + private static String getCookie(String user, String password) throws IOException { + HttpClient httpClient = new HttpClient(); + PostMethod postMethod = new PostMethod(url + "/login"); + postMethod.addRequestHeader("Origin", url); + postMethod.setParameter("password", password); + postMethod.setParameter("userName", user); + httpClient.executeMethod(postMethod); + LOG.info("{} - {}", postMethod.getStatusCode(), postMethod.getStatusText()); + Pattern pattern = Pattern.compile("JSESSIONID=([a-zA-Z0-9-]*)"); + java.util.regex.Matcher matcher = pattern.matcher(postMethod.getResponseHeaders("Set-Cookie")[0].toString()); + return matcher.find()? matcher.group(1) : StringUtils.EMPTY; + } + + protected static boolean userAndPasswordAreNotBlank(String user, String pwd) { + if (StringUtils.isBlank(user) && StringUtils.isBlank(pwd)) { + return false; + } + return true; + } + protected Matcher<HttpMethodBase> responsesWith(final int expectedStatusCode) { return new TypeSafeMatcher<HttpMethodBase>() { WeakReference<HttpMethodBase> method; http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java new file mode 100644 index 0000000..3c5978f --- /dev/null +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/NotebookSecurityRestApiTest.java @@ -0,0 +1,156 @@ +/* + * 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.rest; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.Map; + +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.methods.DeleteMethod; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.server.ZeppelinServer; +import org.hamcrest.Matcher; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +public class NotebookSecurityRestApiTest extends AbstractTestRestApi { + + Gson gson = new Gson(); + + @BeforeClass + public static void init() throws Exception { + AbstractTestRestApi.startUpWithAuthenticationEnable(); + } + + @AfterClass + public static void destroy() throws Exception { + AbstractTestRestApi.shutDown(); + } + + @Before + public void setUp() {} + + @Test + public void testThatUserCanCreateAndRemoveNote() throws IOException { + String noteId = createNoteForUser("test", "admin", "password1"); + assertNotNull(noteId); + String id = getNoteIdForUser(noteId, "admin", "password1"); + assertThat(id, is(noteId)); + deleteNoteForUser(noteId, "admin", "password1"); + } + + @Test + public void testThatOtherUserCanAccessNoteIfPermissionNotSet() throws IOException { + String noteId = createNoteForUser("test", "admin", "password1"); + + userTryGetNote(noteId, "user1", "password2", isAllowed()); + + deleteNoteForUser(noteId, "admin", "password1"); + } + + @Test + public void testThatOtherUserCannotAccessNoteIfPermissionSet() throws IOException { + String noteId = createNoteForUser("test", "admin", "password1"); + + //set permission + String payload = "{ \"owners\": [\"admin\"], \"readers\": [\"user2\"], \"writers\": [\"user2\"] }"; + PutMethod put = httpPut("/notebook/" + noteId + "/permissions", payload , "admin", "password1"); + assertThat("test set note premission method:", put, isAllowed()); + put.releaseConnection(); + + userTryGetNote(noteId, "user1", "password2", isForbiden()); + + userTryGetNote(noteId, "user2", "password3", isAllowed()); + + deleteNoteForUser(noteId, "admin", "password1"); + } + + @Test + public void testThatWriterCannotRemoveNote() throws IOException { + String noteId = createNoteForUser("test", "admin", "password1"); + + //set permission + String payload = "{ \"owners\": [\"admin\", \"user1\"], \"readers\": [\"user2\"], \"writers\": [\"user2\"] }"; + PutMethod put = httpPut("/notebook/" + noteId + "/permissions", payload , "admin", "password1"); + assertThat("test set note premission method:", put, isAllowed()); + put.releaseConnection(); + + userTryRemoveNote(noteId, "user2", "password3", isForbiden()); + userTryRemoveNote(noteId, "user1", "password2", isAllowed()); + + Note deletedNote = ZeppelinServer.notebook.getNote(noteId); + assertNull("Deleted note should be null", deletedNote); + } + + private void userTryRemoveNote(String noteId, String user, String pwd, Matcher<? super HttpMethodBase> m) throws IOException { + DeleteMethod delete = httpDelete(("/notebook/" + noteId), user, pwd); + assertThat(delete, m); + delete.releaseConnection(); + } + + private void userTryGetNote(String noteId, String user, String pwd, Matcher<? super HttpMethodBase> m) throws IOException { + GetMethod get = httpGet("/notebook/" + noteId, user, pwd); + assertThat(get, m); + get.releaseConnection(); + } + + private String getNoteIdForUser(String noteId, String user, String pwd) throws IOException { + GetMethod get = httpGet("/notebook/" + noteId, user, pwd); + assertThat("test note create method:", get, isAllowed()); + Map<String, Object> resp = gson.fromJson(get.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() { + }.getType()); + get.releaseConnection(); + return (String) ((Map<String, Object>)resp.get("body")).get("id"); + } + + private String createNoteForUser(String noteName, String user, String pwd) throws IOException { + String jsonRequest = "{\"name\":\"" + noteName + "\"}"; + PostMethod post = httpPost("/notebook/", jsonRequest, user, pwd); + assertThat("test note create method:", post, isCreated()); + Map<String, Object> resp = gson.fromJson(post.getResponseBodyAsString(), new TypeToken<Map<String, Object>>() { + }.getType()); + post.releaseConnection(); + String newNoteId = (String) resp.get("body"); + Note newNote = ZeppelinServer.notebook.getNote(newNoteId); + assertNotNull("Can not find new note by id", newNote); + return newNoteId; + } + + private void deleteNoteForUser(String noteId, String user, String pwd) throws IOException { + DeleteMethod delete = httpDelete(("/notebook/" + noteId), user, pwd); + assertThat("Test delete method:", delete, isAllowed()); + delete.releaseConnection(); + // make sure note is deleted + if (!noteId.isEmpty()) { + Note deletedNote = ZeppelinServer.notebook.getNote(noteId); + assertNull("Deleted note should be null", deletedNote); + } + } +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java index 88cc4ee..b972fff 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java @@ -344,7 +344,7 @@ public class ZeppelinConfiguration extends XMLConfiguration { public String getNotebookDir() { return getString(ConfVars.ZEPPELIN_NOTEBOOK_DIR); } - + public String getUser() { return getString(ConfVars.ZEPPELIN_NOTEBOOK_S3_USER); } @@ -430,6 +430,10 @@ public class ZeppelinConfiguration extends XMLConfiguration { public boolean isWindowsPath(String path){ return path.matches("^[A-Za-z]:\\\\.*"); } + + public boolean isAnonymousAllowed() { + return getBoolean(ConfVars.ZEPPELIN_ANONYMOUS_ALLOWED); + } public String getConfDir() { return getString(ConfVars.ZEPPELIN_CONF_DIR); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/32fe23f2/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/NotebookAuthorization.java ---------------------------------------------------------------------- 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 c199c96..d835c89 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 @@ -17,18 +17,31 @@ package org.apache.zeppelin.notebook; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.Sets; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.user.AuthenticationInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; -import java.util.*; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; /** * Contains authorization information for notes @@ -239,6 +252,39 @@ public class NotebookAuthorization { return (b.isEmpty() || (intersection.size() > 0)); } + public boolean isOwner(Set<String> userAndRoles, String noteId) { + if (conf.isAnonymousAllowed()) { + LOG.debug("Zeppelin runs in anonymous mode, everybody is owner"); + return true; + } + if (userAndRoles == null) { + return false; + } + return isOwner(noteId, userAndRoles); + } + + public boolean hasWriteAuthorization(Set<String> userAndRoles, String noteId) { + if (conf.isAnonymousAllowed()) { + LOG.debug("Zeppelin runs in anonymous mode, everybody is writer"); + return true; + } + if (userAndRoles == null) { + return false; + } + return isWriter(noteId, userAndRoles); + } + + public boolean hasReadAuthorization(Set<String> userAndRoles, String noteId) { + if (conf.isAnonymousAllowed()) { + LOG.debug("Zeppelin runs in anonymous mode, everybody is reader"); + return true; + } + if (userAndRoles == null) { + return false; + } + return isReader(noteId, userAndRoles); + } + public void removeNote(String noteId) { authInfo.remove(noteId); saveToFile();
