/*
* 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.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.Collections;
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.commons.lang.StringUtils;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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
*/
public class NotebookAuthorization {
private static final Logger LOG = LoggerFactory.getLogger(NotebookAuthorization.class);
private static NotebookAuthorization instance = null;
/*
* { "note1": { "owners": ["u1"], "readers": ["u1", "u2"], "writers": ["u1"] }, "note2": ... } }
*/
private static Map<String, Map<String, Set<String>>> authInfo = new HashMap<>();
/*
* contains roles for each user
*/
private static Map<String, Set<String>> userRoles = new HashMap<>();
private static ZeppelinConfiguration conf;
private static Gson gson;
private static String filePath;
private NotebookAuthorization() {}
public static NotebookAuthorization init(ZeppelinConfiguration config) {
if (instance == null) {
instance = new NotebookAuthorization();
conf = config;
filePath = conf.getNotebookAuthorizationPath();
GsonBuilder builder = new GsonBuilder();
builder.setPrettyPrinting();
gson = builder.create();
try {
loadFromFile();
} catch (IOException e) {
LOG.error("Error loading NotebookAuthorization", e);
}
}
return instance;
}
public static NotebookAuthorization getInstance() {
if (instance == null) {
LOG.warn("Notebook authorization module was called without initialization,"
+ " initializing with default configuration");
init(ZeppelinConfiguration.create());
}
return instance;
}
private static void loadFromFile() throws IOException {
File settingFile = new File(filePath);
LOG.info(settingFile.getAbsolutePath());
if (!settingFile.exists()) {
// nothing to read
return;
}
FileInputStream fis = new FileInputStream(settingFile);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader bufferedReader = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
isr.close();
fis.close();
String json = sb.toString();
NotebookAuthorizationInfoSaving info = gson.fromJson(json,
NotebookAuthorizationInfoSaving.class);
authInfo = info.authInfo;
}
public void setRoles(String user, Set<String> roles) {
if (StringUtils.isBlank(user)) {
LOG.warn("Setting roles for empty user");
return;
}
roles = validateUser(roles);
userRoles.put(user, roles);
}
public Set<String> getRoles(String user) {
Set<String> roles = Sets.newHashSet();
if (userRoles.containsKey(user)) {
roles.addAll(userRoles.get(user));
}
return roles;
}
private void saveToFile() {
String jsonString;
synchronized (authInfo) {
NotebookAuthorizationInfoSaving info = new NotebookAuthorizationInfoSaving();
info.authInfo = authInfo;
jsonString = gson.toJson(info);
}
try {
File settingFile = new File(filePath);
if (!settingFile.exists()) {
settingFile.createNewFile();
}
FileOutputStream fos = new FileOutputStream(settingFile, false);
OutputStreamWriter out = new OutputStreamWriter(fos);
out.append(jsonString);
out.close();
fos.close();
} catch (IOException e) {
LOG.error("Error saving notebook authorization file: " + e.getMessage());
}
}
public boolean isPublic() {
return conf.isNotebokPublic();
}
private Set<String> validateUser(Set<String> users) {
Set<String> returnUser = new HashSet<>();
for (String user : users) {
if (!user.trim().isEmpty()) {
returnUser.add(user.trim());
}
}
return returnUser;
}
public void setOwners(String noteId, Set<String> entities) {
Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
entities = validateUser(entities);
if (noteAuthInfo == null) {
noteAuthInfo = new LinkedHashMap();
noteAuthInfo.put("owners", new LinkedHashSet(entities));
noteAuthInfo.put("readers", new LinkedHashSet());
noteAuthInfo.put("writers", new LinkedHashSet());
} else {
noteAuthInfo.put("owners", new LinkedHashSet(entities));
}
authInfo.put(noteId, noteAuthInfo);
saveToFile();
}
public void setReaders(String noteId, Set<String> entities) {
Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
entities = validateUser(entities);
if (noteAuthInfo == null) {
noteAuthInfo = new LinkedHashMap();
noteAuthInfo.put("owners", new LinkedHashSet());
noteAuthInfo.put("readers", new LinkedHashSet(entities));
noteAuthInfo.put("writers", new LinkedHashSet());
} else {
noteAuthInfo.put("readers", new LinkedHashSet(entities));
}
authInfo.put(noteId, noteAuthInfo);
saveToFile();
}
public void setWriters(String noteId, Set<String> entities) {
Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
entities = validateUser(entities);
if (noteAuthInfo == null) {
noteAuthInfo = new LinkedHashMap();
noteAuthInfo.put("owners", new LinkedHashSet());
noteAuthInfo.put("readers", new LinkedHashSet());
noteAuthInfo.put("writers", new LinkedHashSet(entities));
} else {
noteAuthInfo.put("writers", new LinkedHashSet(entities));
}
authInfo.put(noteId, noteAuthInfo);
saveToFile();
}
public Set<String> getOwners(String noteId) {
Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
Set<String> entities = null;
if (noteAuthInfo == null) {
entities = new HashSet<>();
} else {
entities = noteAuthInfo.get("owners");
if (entities == null) {
entities = new HashSet<>();
}
}
return entities;
}
public Set<String> getReaders(String noteId) {
Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
Set<String> entities = null;
if (noteAuthInfo == null) {
entities = new HashSet<>();
} else {
entities = noteAuthInfo.get("readers");
if (entities == null) {
entities = new HashSet<>();
}
}
return entities;
}
public Set<String> getWriters(String noteId) {
Map<String, Set<String>> noteAuthInfo = authInfo.get(noteId);
Set<String> entities = null;
if (noteAuthInfo == null) {
entities = new HashSet<>();
} else {
entities = noteAuthInfo.get("writers");
if (entities == null) {
entities = new HashSet<>();
}
}
return entities;
}
public boolean isOwner(String noteId, Set<String> entities) {
return isMember(entities, getOwners(noteId));
}
public boolean isWriter(String noteId, Set<String> entities) {
return isMember(entities, getWriters(noteId)) || isMember(entities, getOwners(noteId));
}
public boolean isReader(String noteId, Set<String> entities) {
return isMember(entities, getReaders(noteId)) ||
isMember(entities, getOwners(noteId)) ||
isMember(entities, getWriters(noteId));
}
// return true if b is empty or if (a intersection b) is non-empty
private boolean isMember(Set<String> a, Set<String> b) {
Set<String> intersection = new HashSet<>(b);
intersection.retainAll(a);
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();
}
public List<NoteInfo> filterByUser(List<NoteInfo> notes, AuthenticationInfo subject) {
final Set<String> entities = Sets.newHashSet();
if (subject != null) {
entities.add(subject.getUser());
}
return FluentIterable.from(notes).filter(new Predicate<NoteInfo>() {
@Override
public boolean apply(NoteInfo input) {
return input != null && isReader(input.getId(), entities);
}
}).toList();
}
public void setNewNotePermissions(String noteId, AuthenticationInfo subject) {
if (!AuthenticationInfo.isAnonymous(subject)) {
if (isPublic()) {
// add current user to owners - can be public
Set<String> owners = getOwners(noteId);
owners.add(subject.getUser());
setOwners(noteId, owners);
} else {
// add current user to owners, readers, writers - private note
Set<String> entities = getOwners(noteId);
entities.add(subject.getUser());
setOwners(noteId, entities);
entities = getReaders(noteId);
entities.add(subject.getUser());
setReaders(noteId, entities);
entities = getWriters(noteId);
entities.add(subject.getUser());
setWriters(noteId, entities);
}
}
}
}