fix #998 add AuthenticationManager
This commit is contained in:
parent
d68320dfde
commit
66e3a91e5f
8 changed files with 490 additions and 11 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
299
src/main/java/org/codelibs/fess/auth/chain/CommandChain.java
Normal file
299
src/main/java/org/codelibs/fess/auth/chain/CommandChain.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
45
src/main/java/org/codelibs/fess/auth/chain/LdapChain.java
Normal file
45
src/main/java/org/codelibs/fess/auth/chain/LdapChain.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue