fix #998 add AuthenticationManager

This commit is contained in:
Shinsuke Sugaya 2017-04-15 23:11:04 +09:00
parent d68320dfde
commit 66e3a91e5f
8 changed files with 490 additions and 11 deletions

View file

@ -61,10 +61,7 @@ public class UserService {
}
public OptionalEntity<User> getUser(final String id) {
return userBhv.selectByPK(id).map(u -> {
ComponentUtil.getLdapManager().apply(u);
return u;
});
return userBhv.selectByPK(id).map(u -> ComponentUtil.getAuthenticationManager().load(u));
}
public OptionalEntity<User> getUserByName(final String username) {
@ -78,7 +75,7 @@ public class UserService {
user.setSurname(user.getName());
}
ComponentUtil.getLdapManager().insert(user);
ComponentUtil.getAuthenticationManager().insert(user);
userBhv.insertOrUpdate(user, op -> {
op.setRefreshPolicy(Constants.TRUE);
@ -87,10 +84,8 @@ public class UserService {
}
public void changePassword(final String username, final String password) {
final boolean changed = ComponentUtil.getLdapManager().changePassword(username, password);
final FessConfig fessConfig = ComponentUtil.getFessConfig();
if (!changed || fessConfig.isLdapAdminSyncPassword()) {
final boolean changed = ComponentUtil.getAuthenticationManager().changePassword(username, password);
if (changed) {
userBhv.selectEntity(cb -> cb.query().setName_Equal(username)).ifPresent(entity -> {
final String encodedPassword = fessLoginAssist.encryptPassword(password);
entity.setPassword(encodedPassword);
@ -103,7 +98,7 @@ public class UserService {
}
public void delete(final User user) {
ComponentUtil.getLdapManager().delete(user);
ComponentUtil.getAuthenticationManager().delete(user);
userBhv.delete(user, op -> {
op.setRefreshPolicy(Constants.TRUE);

View file

@ -0,0 +1,57 @@
/*
* Copyright 2012-2017 CodeLibs Project and the Others.
*
* Licensed 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.codelibs.fess.auth;
import static org.codelibs.core.stream.StreamUtil.stream;
import org.apache.commons.lang3.ArrayUtils;
import org.codelibs.core.stream.StreamUtil.StreamOf;
import org.codelibs.fess.auth.chain.AuthenticationChain;
import org.codelibs.fess.es.user.exentity.User;
public class AuthenticationManager {
protected AuthenticationChain[] chains = new AuthenticationChain[0];
public void insert(final User user) {
chains().of(stream -> stream.forEach(c -> c.update(user)));
}
public boolean changePassword(final String username, final String password) {
return chains().get(stream -> stream.allMatch(c -> c.changePassword(username, password)));
}
public void delete(final User user) {
chains().of(stream -> stream.forEach(c -> c.delete(user)));
}
public User load(final User user) {
User u = user;
for (final AuthenticationChain chain : chains) {
u = chain.load(u);
}
return u;
}
public void addChain(final AuthenticationChain chain) {
chains = ArrayUtils.addAll(chains, chain);
}
protected StreamOf<AuthenticationChain> chains() {
return stream(chains);
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2012-2017 CodeLibs Project and the Others.
*
* Licensed 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.codelibs.fess.auth.chain;
import org.codelibs.fess.es.user.exentity.User;
public interface AuthenticationChain {
void update(User user);
void delete(User user);
boolean changePassword(String username, String password);
User load(User user);
}

View file

@ -0,0 +1,299 @@
/*
* Copyright 2012-2017 CodeLibs Project and the Others.
*
* Licensed 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.codelibs.fess.auth.chain;
import static org.codelibs.core.stream.StreamUtil.stream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;
import org.codelibs.core.lang.StringUtil;
import org.codelibs.fess.crawler.Constants;
import org.codelibs.fess.crawler.exception.CrawlerSystemException;
import org.codelibs.fess.es.user.exentity.User;
import org.codelibs.fess.exception.CommandExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CommandChain implements AuthenticationChain {
private static final Logger logger = LoggerFactory.getLogger(CommandChain.class);
protected File workingDirectory = null;
protected int maxOutputLine = 1000;
protected long executionTimeout = 30L * 1000L; // 30sec
protected String commandOutputEncoding = System.getProperty("file.encoding");
protected String[] updateCommand;
protected String[] deleteCommand;
protected String[] targetUsers;
@Override
public void update(final User user) {
final String username = user.getName();
final String password = user.getOriginalPassword();
changePassword(username, password);
}
@Override
public void delete(final User user) {
final String username = user.getName();
if (isTargetUser(username)) {
executeCommand(deleteCommand, username, StringUtil.EMPTY);
}
}
@Override
public boolean changePassword(final String username, final String password) {
if (isTargetUser(username) && StringUtil.isNotBlank(password)) {
return executeCommand(updateCommand, username, password) == 0;
}
return true;
}
@Override
public User load(final User user) {
return user;
}
protected boolean isTargetUser(final String username) {
if (targetUsers == null) {
return true;
}
return stream(targetUsers).get(stream -> stream.anyMatch(s -> s.equals(username)));
}
protected int executeCommand(final String[] commands, final String username, final String password) {
if (commands == null || commands.length == 0) {
throw new CommandExecutionException("command is empty.");
}
if (logger.isInfoEnabled()) {
logger.info("Command: " + String.join(" ", commands));
}
final ProcessBuilder pb = new ProcessBuilder((String[]) stream(commands).get(stream -> stream.map(s -> {
if ("$USERNAME".equals(s)) {
return username;
} else if ("$PASSWORD".equals(s)) {
return password;
} else {
return s;
}
}).toArray(n -> new String[n])));
if (workingDirectory != null) {
pb.directory(workingDirectory);
}
pb.redirectErrorStream(true);
Process currentProcess = null;
MonitorThread mt = null;
try {
currentProcess = pb.start();
// monitoring
mt = new MonitorThread(currentProcess, executionTimeout);
mt.start();
final InputStreamThread it = new InputStreamThread(currentProcess.getInputStream(), commandOutputEncoding, maxOutputLine);
it.start();
currentProcess.waitFor();
it.join(5000);
if (mt.isTeminated()) {
throw new CommandExecutionException("The command execution is timeout: " + String.join(" ", commands));
}
final int exitValue = currentProcess.exitValue();
if (logger.isInfoEnabled()) {
logger.info("Exit Code: " + exitValue + " - Process Output:\n" + it.getOutput());
}
if (exitValue == 143 && mt.isTeminated()) {
throw new CommandExecutionException("The command execution is timeout: " + String.join(" ", commands));
}
return exitValue;
} catch (final CrawlerSystemException e) {
throw e;
} catch (final InterruptedException e) {
if (mt != null && mt.isTeminated()) {
throw new CommandExecutionException("The command execution is timeout: " + String.join(" ", commands), e);
}
throw new CommandExecutionException("Process terminated.", e);
} catch (final Exception e) {
throw new CommandExecutionException("Process terminated.", e);
} finally {
if (mt != null) {
mt.setFinished(true);
try {
mt.interrupt();
} catch (final Exception e) {
// ignore
}
}
if (currentProcess != null) {
try {
currentProcess.destroy();
} catch (final Exception e) {
// ignore
}
}
currentProcess = null;
}
}
protected static class MonitorThread extends Thread {
private final Process process;
private final long timeout;
private boolean finished = false;
private boolean teminated = false;
public MonitorThread(final Process process, final long timeout) {
super();
this.process = process;
this.timeout = timeout;
}
@Override
public void run() {
try {
Thread.sleep(timeout);
} catch (final InterruptedException e) {
// ignore
}
if (!finished) {
try {
process.destroy();
teminated = true;
} catch (final Exception e) {
if (logger.isInfoEnabled()) {
logger.info("Could not kill the subprocess.", e);
}
}
}
}
/**
* @param finished
* The finished to set.
*/
public void setFinished(final boolean finished) {
this.finished = finished;
}
/**
* @return Returns the teminated.
*/
public boolean isTeminated() {
return teminated;
}
}
protected static class InputStreamThread extends Thread {
private BufferedReader br;
private final List<String> list = new LinkedList<>();
private final int maxLineBuffer;
public InputStreamThread(final InputStream is, final String charset, final int maxOutputLineBuffer) {
super();
try {
br = new BufferedReader(new InputStreamReader(is, charset));
} catch (final UnsupportedEncodingException e) {
br = new BufferedReader(new InputStreamReader(is, Constants.UTF_8_CHARSET));
}
maxLineBuffer = maxOutputLineBuffer;
}
@Override
public void run() {
for (;;) {
try {
final String line = br.readLine();
if (line == null) {
break;
}
if (logger.isDebugEnabled()) {
logger.debug(line);
}
list.add(line);
if (list.size() > maxLineBuffer) {
list.remove(0);
}
} catch (final IOException e) {
throw new CrawlerSystemException(e);
}
}
}
public String getOutput() {
final StringBuilder buf = new StringBuilder(100);
for (final String value : list) {
buf.append(value).append("\n");
}
return buf.toString();
}
}
public void setWorkingDirectory(final File workingDirectory) {
this.workingDirectory = workingDirectory;
}
public void setMaxOutputLine(final int maxOutputLine) {
this.maxOutputLine = maxOutputLine;
}
public void setExecutionTimeout(final long executionTimeout) {
this.executionTimeout = executionTimeout;
}
public void setCommandOutputEncoding(final String commandOutputEncoding) {
this.commandOutputEncoding = commandOutputEncoding;
}
public void setUpdateCommand(final String[] updateCommand) {
this.updateCommand = updateCommand;
}
public void setDeleteCommand(final String[] deleteCommand) {
this.deleteCommand = deleteCommand;
}
public void setTargetUsers(final String[] targetUsers) {
this.targetUsers = targetUsers;
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2012-2017 CodeLibs Project and the Others.
*
* Licensed 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.codelibs.fess.auth.chain;
import org.codelibs.fess.es.user.exentity.User;
import org.codelibs.fess.util.ComponentUtil;
public class LdapChain implements AuthenticationChain {
@Override
public void update(final User user) {
ComponentUtil.getLdapManager().insert(user);
}
@Override
public void delete(final User user) {
ComponentUtil.getLdapManager().delete(user);
}
@Override
public boolean changePassword(final String username, final String password) {
boolean changed = ComponentUtil.getLdapManager().changePassword(username, password);
return !changed || ComponentUtil.getFessConfig().isLdapAdminSyncPassword();
}
@Override
public User load(final User user) {
ComponentUtil.getLdapManager().apply(user);
return user;
}
}

View file

@ -0,0 +1,15 @@
package org.codelibs.fess.exception;
public class CommandExecutionException extends FessSystemException {
private static final long serialVersionUID = 1L;
public CommandExecutionException(String message) {
super(message);
}
public CommandExecutionException(String message, Throwable e) {
super(message, e);
}
}

View file

@ -23,6 +23,7 @@ import org.apache.lucene.queryparser.classic.QueryParser;
import org.codelibs.core.crypto.CachedCipher;
import org.codelibs.core.misc.DynamicProperties;
import org.codelibs.fess.api.WebApiManagerFactory;
import org.codelibs.fess.auth.AuthenticationManager;
import org.codelibs.fess.crawler.client.CrawlerClientFactory;
import org.codelibs.fess.crawler.entity.EsAccessResult;
import org.codelibs.fess.crawler.extractor.ExtractorFactory;
@ -77,6 +78,8 @@ public final class ComponentUtil {
private static final Logger logger = LoggerFactory.getLogger(ComponentUtil.class);
private static final String AUTHENTICATION_MANAGER = "authenticationManager";
private static final String THUMBNAIL_MANAGER = "thumbnailManager";
private static final String SSO_MANAGER = "ssoManager";
@ -378,6 +381,10 @@ public final class ComponentUtil {
return getComponent(THUMBNAIL_MANAGER);
}
public static AuthenticationManager getAuthenticationManager() {
return getComponent(AUTHENTICATION_MANAGER);
}
public static PrimaryCipher getPrimaryCipher() {
return getComponent(PrimaryCipher.class);
}

View file

@ -321,7 +321,6 @@
<arg>"labels.facet_filetype_others"</arg>
<arg>"filetype:others"</arg>
</postConstruct>
</component>
</arg>
</postConstruct>
@ -332,6 +331,38 @@
</component>
<component name="userInfoHelper" class="org.codelibs.fess.helper.UserInfoHelper">
</component>
<component name="authenticationManager" class="org.codelibs.fess.auth.AuthenticationManager">
<!--
<postConstruct name="addChain">
<arg>
<component class="org.codelibs.fess.auth.chain.CommandChain">
<property name="updateCommand">[
"/usr/sbin/htpasswd",
"-b",
"/tmp/test.txt",
"$USERNAME",
"$PASSWORD"
]</property>
<property name="deleteCommand">[
"/usr/sbin/htpasswd",
"-D",
"/tmp/test.txt",
"$USERNAME"
]</property>
<property name="targetUsers">[
"admin"
]</property>
</component>
</arg>
</postConstruct>
-->
<postConstruct name="addChain">
<arg>
<component class="org.codelibs.fess.auth.chain.LdapChain">
</component>
</arg>
</postConstruct>
</component>
<component name="openSearchHelper" class="org.codelibs.fess.helper.OpenSearchHelper">
<property name="osddPath">"/WEB-INF/orig/open-search/osdd.xml"</property>
<property name="encoding">"UTF-8"</property>