Commit 2266beb7 by Tuukka Kivilahti

Merge branch 'sorry' into 'master'

Sorry

See merge request !413
2 parents d26b9e67 6028ba95
package fi.codecrew.moya.beans.auth;
import javax.ejb.Local;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
@Local
public interface AuthHelperBeanLocal {
String parseHostname(HttpServletRequest httpRequest);
RestAuthResponse parseRestAuthCredentials(HttpServletRequest httpRequest) throws ServletException;
class RestAuthResponse {
public final String username;
public final String password;
public RestAuthResponse(String username, String password) {
this.username = username;
this.password = password;
}
}
}
...@@ -168,7 +168,7 @@ public class PermissionBean implements PermissionBeanLocal { ...@@ -168,7 +168,7 @@ public class PermissionBean implements PermissionBeanLocal {
@Override @Override
public boolean isLoggedIn() { public boolean isLoggedIn() {
return !User.ANONYMOUS_LOGINNAME.equalsIgnoreCase(getPrincipalName()); return getPrincipalName() != null && !User.ANONYMOUS_LOGINNAME.equalsIgnoreCase(getPrincipalName());
} }
@Override @Override
......
package fi.codecrew.moya.beans.auth;
import fi.codecrew.moya.beans.ApiApplicationBeanLocal;
import fi.codecrew.moya.beans.JaasBeanLocal;
import fi.codecrew.moya.beans.LoggingBeanLocal;
import fi.codecrew.moya.utilities.UserLoginUtils;
import fi.codecrew.moya.utilities.moyamessage.MoyaEventType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.Base64;
@LocalBean
@Stateless
public class AuthHelperBean implements AuthHelperBeanLocal {
private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
private static final Logger logger = LoggerFactory.getLogger(AuthHelperBean.class);
@EJB
private LoggingBeanLocal logbean;
@EJB
private ApiApplicationBeanLocal apibean;
@Override
public String parseHostname(HttpServletRequest httpRequest) {
StringBuffer url = httpRequest.getRequestURL();
String requestHostHeader = httpRequest.getHeader("host");
String hostname = "localhost";
if (requestHostHeader != null) {
hostname = requestHostHeader.split(":")[0];
}
// if proxy provides scheme in header, use it..
String scheme = httpRequest.getHeader(X_FORWARDED_PROTO);
if (scheme == null || scheme.isEmpty()) {
scheme = url.substring(0, 5).toLowerCase();
}
Principal principal = httpRequest.getUserPrincipal();
if (principal != null) {
String userDomain = UserLoginUtils.getDomainFromJaas(principal);
// If there is no logged-in user, we can and should not check userDomain against hostname
if (principal != null && !hostname.equals(userDomain)) {
logbean.sendMessage(MoyaEventType.USER_PERMISSION_VIOLATION,
"Hostname mismatch privilege escalation! User '", httpRequest.getUserPrincipal(), "' tried to change hostname from '",
userDomain, "' to '", hostname, ",");
throw new RuntimeException("Hostname mismatch! Expected: " + hostname + " but logged in as " + userDomain);
}
}
return hostname;
}
/**
* Check if we should
*
* @param httpRequest
* @return Return wether rest failed. If it was not tried, function will return true, which
* will allow other authentication methods to be tried.
*/
public AuthHelperBeanLocal.RestAuthResponse parseRestAuthCredentials(HttpServletRequest httpRequest) throws ServletException {
// Allow api auth with password only if ssl is enabled ( or in
// development mode... )
// if (BortalLocalContextHolder.isSsl() ||
// BortalLocalContextHolder.isInDevelopmentMode()) {
String restAuthStr = httpRequest.getHeader("Authorization");
// }
String userkey = null;
String appkey = null;
// Payara got updated, and does not allow changing of username anymore, so we must send
// username and domain to initial jaas-login query...
if (restAuthStr != null) {
String decodedStr = new String(Base64.getDecoder().decode(restAuthStr.split(" ")[1]), StandardCharsets.UTF_8);
if(!decodedStr.startsWith("appauth:")){
return null;
}
String[] splitStr = decodedStr.split(":");
logger.warn("SplitDecoded {}", decodedStr);
if(splitStr.length < 3){
return null;
}
appkey = splitStr[1];
userkey = splitStr[2];
// final String appKey = splitStr[3];
} else if (httpRequest.getParameter("appkey") != null) {
appkey = httpRequest.getParameter("appkey");
userkey = httpRequest.getParameter("appuser");
StringBuilder hashBuilder = new StringBuilder();
hashBuilder.append(JaasBeanLocal.REST_PREFIX);
hashBuilder.append(appkey).append(":");
hashBuilder.append(userkey).append(":");
hashBuilder.append(httpRequest.getParameter("appstamp")).append(":");
hashBuilder.append(httpRequest.getParameter("appmac")).append(":");
hashBuilder.append(httpRequest.getPathInfo());
restAuthStr = hashBuilder.toString();
} else {
// If no rest authentication is even tried, allow, anon login
return null;
}
String domain = parseHostname(httpRequest);
if (restAuthStr == null) {
throw new ServletException("No auth data");
}
//final String username = "@" + parseHostname(httpRequest);
String userLogin = apibean.findUsernameForApikey(appkey, userkey, domain);
if (userLogin == null) {
throw new ServletException("Unable to find username for credentials");
}
return new RestAuthResponse(userLogin + '@' + domain, restAuthStr);
}
}
...@@ -23,15 +23,13 @@ ...@@ -23,15 +23,13 @@
package fi.codecrew.moya.model; package fi.codecrew.moya.model;
import java.security.MessageDigest; import java.time.Instant;
import java.security.NoSuchAlgorithmException; import java.time.ZoneId;
import java.util.Calendar; import java.time.format.DateTimeFormatter;
import java.util.Date; import java.util.Date;
import javax.persistence.Basic;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.Lob; import javax.persistence.Lob;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
...@@ -39,12 +37,11 @@ import javax.persistence.Table; ...@@ -39,12 +37,11 @@ import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* *
*/ */
@Entity @Entity
@Table(name = "compo_entry_files") @Table(name = "compo_entry_files")
...@@ -53,6 +50,8 @@ public class CompoEntryFile extends GenericEntity { ...@@ -53,6 +50,8 @@ public class CompoEntryFile extends GenericEntity {
private static final Logger logger = LoggerFactory.getLogger(CompoEntryFile.class); private static final Logger logger = LoggerFactory.getLogger(CompoEntryFile.class);
public static final String DEFAULT_FILENAMEPATTERN = "{entry.id}-{entry.title}-{filetype.name}-v{file.version}-{file.id}{file.origfilename}";
@Column(name = "mime_type") @Column(name = "mime_type")
private String mimeType; private String mimeType;
...@@ -66,21 +65,22 @@ public class CompoEntryFile extends GenericEntity { ...@@ -66,21 +65,22 @@ public class CompoEntryFile extends GenericEntity {
@Column(name = "hash", updatable = false) @Column(name = "hash", updatable = false)
private String hash; private String hash;
@Column(name="filepath", updatable = false, nullable = false) @Column(name = "filepath", updatable = false, nullable = false)
private String filepath; private String filepath;
@Column(name = "uploaded", nullable = false) @Column(name = "uploaded", nullable = false)
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date uploaded = new Date(); private Date uploaded = new Date();
@ManyToOne(optional=false) @ManyToOne(optional = false)
@JoinColumn(name="entryfiletype_id", referencedColumnName = "id", nullable = false, updatable = false) @JoinColumn(name = "entryfiletype_id", referencedColumnName = "id", nullable = false, updatable = false)
private CompoEntryFileType filetype; private CompoEntryFileType filetype;
public CompoEntryFile() { public CompoEntryFile() {
super(); super();
} }
public String getMimeType() { public String getMimeType() {
return mimeType; return mimeType;
} }
...@@ -137,4 +137,41 @@ public class CompoEntryFile extends GenericEntity { ...@@ -137,4 +137,41 @@ public class CompoEntryFile extends GenericEntity {
public void setFilepath(String filepath) { public void setFilepath(String filepath) {
this.filepath = filepath; this.filepath = filepath;
} }
public Long getVersionNumber() {
return 1 + getFiletype().getEntryFiles().stream().filter(file -> file.getUploaded().before(getUploaded())).count();
}
public String getFilenameFromPattern(String pattern) {
final CompoEntry entry = getFiletype().getEntry();
final Compo compo = entry.getCompo();
final CompoFileType type = getFiletype().getType();
return pattern
.replace("{file.id}", getId().toString())
.replace("{file.version}", getVersionNumber().toString())
.replace("{file.origfilename}", getFileName())
.replace("{file.hash}", getHash())
.replace("{file.uploaded}", formatDateteime(uploaded))
.replace("{filetype.name}", type.getName())
.replace("{filetype.id}", type.getId().toString())
.replace("{compo.id}", compo.getId().toString())
.replace("{compo.name}", compo.getName())
.replace("{entry.id}", entry.getId().toString())
.replace("{entry.title}", entry.getTitle())
.replace("{entry.sort}", entry.getSort().toString())
.replaceAll("[^0-9a-zA-Z._-]", "_");
}
private static String formatDateteime(Date d) {
return Instant
.ofEpochMilli(d.getTime())
.atZone(ZoneId.systemDefault())
.toOffsetDateTime()
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<artifactId>moya-restpojo</artifactId> <artifactId>moya-restpojo</artifactId>
<!-- This is set here on purpose, so that remote dependencies do not break <!-- This is set here on purpose, so that remote dependencies do not break
If this is updated. remember to update also version in moya-web --> If this is updated. remember to update also version in moya-web -->
<version>1.2.4</version> <version>1.2.6</version>
<distributionManagement> <distributionManagement>
<downloadUrl>http://codecrew.fi/mvn</downloadUrl> <downloadUrl>http://codecrew.fi/mvn</downloadUrl>
<repository> <repository>
...@@ -48,8 +48,14 @@ ...@@ -48,8 +48,14 @@
<dependency> <dependency>
<groupId>io.swagger.core.v3</groupId> <groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId> <artifactId>swagger-annotations</artifactId>
<version>${swaggerv3.version}</version> <version>2.0.2</version>
</dependency> </dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.4</version>
</dependency>
</dependencies> </dependencies>
<parent> <parent>
......
...@@ -11,6 +11,9 @@ ...@@ -11,6 +11,9 @@
<h:outputLabel value="#{i18n['vip.host']}" for="host" /> <h:outputLabel value="#{i18n['vip.host']}" for="host" />
<h:outputText id="host" value="#{vipDeliverView.vip.host.wholeName}" /> <h:outputText id="host" value="#{vipDeliverView.vip.host.wholeName}" />
<h:outputLabel for="shortdescr" value="#{i18n['vip.shortdescr']}" />
<h:outputText id="shortdescr" value="#{vipDeliverView.vip.shortdescr}" />
<h:outputLabel for="description" value="#{i18n['vip.description']}" /> <h:outputLabel for="description" value="#{i18n['vip.description']}" />
<h:outputText id="description" value="#{vipDeliverView.vip.description}" /> <h:outputText id="description" value="#{vipDeliverView.vip.description}" />
</h:panelGrid> </h:panelGrid>
......
...@@ -81,6 +81,9 @@ ...@@ -81,6 +81,9 @@
<p:column headerText="CurrentFile"> <p:column headerText="CurrentFile">
<h:outputText value="X" rendered="#{entryEditView.selectedFiletype.currentFile==file}"/> <h:outputText value="X" rendered="#{entryEditView.selectedFiletype.currentFile==file}"/>
</p:column> </p:column>
<p:column headerText="Ver">
<h:outputText value="#{file.versionNumber}"/>
</p:column>
<p:column headerText="#{i18n['compofile.fileName']}"> <p:column headerText="#{i18n['compofile.fileName']}">
<a href="#{request.contextPath}/EntryFile/#{file.id}"> <a href="#{request.contextPath}/EntryFile/#{file.id}">
<h:outputText value="#{file.fileName}"/> <h:outputText value="#{file.fileName}"/>
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
<dependency> <dependency>
<groupId>fi.codecrew.moya</groupId> <groupId>fi.codecrew.moya</groupId>
<artifactId>moya-restpojo</artifactId> <artifactId>moya-restpojo</artifactId>
<version>1.2.4</version> <version>1.2.6</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.ocpsoft.rewrite</groupId> <groupId>org.ocpsoft.rewrite</groupId>
......
...@@ -18,37 +18,27 @@ ...@@ -18,37 +18,27 @@
*/ */
package fi.codecrew.moya; package fi.codecrew.moya;
import java.io.IOException; import fi.codecrew.moya.beans.*;
import java.nio.charset.Charset; import fi.codecrew.moya.beans.auth.AuthHelperBeanLocal;
import java.nio.charset.StandardCharsets; import fi.codecrew.moya.clientutils.BortalLocalContextHolder;
import java.security.Principal; import fi.codecrew.moya.model.User;
import java.util.Base64; import fi.codecrew.moya.rest.RestApplicationEntrypoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import javax.ejb.EJB; import javax.ejb.EJB;
import javax.faces.application.ProjectStage; import javax.faces.application.ProjectStage;
import javax.faces.context.FacesContext; import javax.faces.context.FacesContext;
import javax.servlet.Filter; import javax.servlet.*;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import java.io.IOException;
import java.nio.charset.Charset;
import fi.codecrew.moya.beans.*; import java.nio.charset.StandardCharsets;
import fi.codecrew.moya.utilities.UserLoginUtils; import java.security.Principal;
import org.apache.commons.codec.Charsets; import java.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import fi.codecrew.moya.clientutils.BortalLocalContextHolder;
import fi.codecrew.moya.model.User;
import fi.codecrew.moya.rest.RestApplicationEntrypoint;
import fi.codecrew.moya.utilities.moyamessage.MoyaEventType;
/** /**
* Servlet Filter implementation class HostnameFilter * Servlet Filter implementation class HostnameFilter
...@@ -56,13 +46,15 @@ import fi.codecrew.moya.utilities.moyamessage.MoyaEventType; ...@@ -56,13 +46,15 @@ import fi.codecrew.moya.utilities.moyamessage.MoyaEventType;
@WebFilter(filterName = "hostnameFilter", displayName = "hostname and authentication filter", urlPatterns = {"/*"}) @WebFilter(filterName = "hostnameFilter", displayName = "hostname and authentication filter", urlPatterns = {"/*"})
public class HostnameFilter implements Filter { public class HostnameFilter implements Filter {
private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
private static final String SESSION_HOSTNAMESTORE = "moya-session-hostname"; private static final String SESSION_HOSTNAMESTORE = "moya-session-hostname";
private static final Logger logger = LoggerFactory.getLogger(HostnameFilter.class); private static final Logger logger = LoggerFactory.getLogger(HostnameFilter.class);
private static final String HTTP_HOSTNAME_ID = "moya_hostname_session_id"; private static final String HTTP_HOSTNAME_ID = "moya_hostname_session_id";
private boolean developmentMode = false; private boolean developmentMode = false;
@EJB @EJB
private AuthHelperBeanLocal authHelper;
@EJB
private RestBeanLocal restauth; private RestBeanLocal restauth;
@EJB @EJB
private LoggingBeanLocal logbean; private LoggingBeanLocal logbean;
...@@ -169,13 +161,13 @@ public class HostnameFilter implements Filter { ...@@ -169,13 +161,13 @@ public class HostnameFilter implements Filter {
chain.doFilter(request, response); chain.doFilter(request, response);
return; return;
} }
BortalLocalContextHolder.setInDevelopmentMode(developmentMode);
try { try {
httpRequest = (HttpServletRequest) request; httpRequest = (HttpServletRequest) request;
insertServerLoggingContext(httpRequest, authtype); insertServerLoggingContext(httpRequest, authtype);
logger.info("Logging in with username {} and password {}, remote {}, authtype: {}", httpRequest.getUserPrincipal(), httpRequest.getRemoteUser(), httpRequest.getAuthType());
String hostname = parseHostname(httpRequest); String hostname = authHelper.parseHostname(httpRequest);
if (httpRequest.getUserPrincipal() == null) { if (httpRequest.getUserPrincipal() == null) {
// Check if we are can login in with rest alternative methods ( appkey, basic auth, etc.. ) // Check if we are can login in with rest alternative methods ( appkey, basic auth, etc.. )
...@@ -184,9 +176,9 @@ public class HostnameFilter implements Filter { ...@@ -184,9 +176,9 @@ public class HostnameFilter implements Filter {
authtype = AuthType.REST; authtype = AuthType.REST;
if (!restAuth(httpRequest, response) && RestApplicationEntrypoint.REST_PATH.equals(httpRequest.getServletPath())) { if (!tryRestAuth(httpRequest, response) && RestApplicationEntrypoint.REST_PATH.equals(httpRequest.getServletPath())) {
response.reset(); response.reset();
response.getOutputStream().write("Rest auth failed! ".getBytes( StandardCharsets.UTF_8)); response.getOutputStream().write("Rest auth failed! ".getBytes(StandardCharsets.UTF_8));
if (response instanceof HttpServletResponse) { if (response instanceof HttpServletResponse) {
HttpServletResponse httpResp = (HttpServletResponse) response; HttpServletResponse httpResp = (HttpServletResponse) response;
...@@ -231,19 +223,12 @@ public class HostnameFilter implements Filter { ...@@ -231,19 +223,12 @@ public class HostnameFilter implements Filter {
} }
} }
// public static String getCurrentHostname(HttpSession sess) { /**
// String ret = null; * @param httpRequest
// if (sess != null) { * @param response
// Object retObj = sess.getAttribute(EventBeanLocal.HTTP_URL_HOSTNAME); * @return Return false, if rest auth failed, and no other authenthication methods should be tried
// if (retObj != null) { */
// ret = retObj.toString(); private boolean tryRestAuth(HttpServletRequest httpRequest, ServletResponse response) {
// }
// }
// return ret;
// }
private boolean restAuth(HttpServletRequest httpRequest, ServletResponse response) {
String sp = httpRequest.getPathInfo(); String sp = httpRequest.getPathInfo();
for (String s : NOAUTH_RESTPATHS) { for (String s : NOAUTH_RESTPATHS) {
if (sp.startsWith(s)) { if (sp.startsWith(s)) {
...@@ -251,99 +236,29 @@ public class HostnameFilter implements Filter { ...@@ -251,99 +236,29 @@ public class HostnameFilter implements Filter {
} }
} }
String restAuthStr = null;
// Allow api auth with password only if ssl is enabled ( or in
// development mode... )
// if (BortalLocalContextHolder.isSsl() ||
// BortalLocalContextHolder.isInDevelopmentMode()) {
restAuthStr = httpRequest.getHeader("Authorization");
// }
String userkey = null;
String appkey = null;
// Payara got updated, and does not allow changing of username anymore, so we must send
// username and domain to initial jaas-login query...
if (restAuthStr != null) {
String decodedStr = new String(Base64.getDecoder().decode(restAuthStr.split(" ")[1]), UTF8);
String[] splitStr = decodedStr.split(":");
appkey = splitStr[1];
userkey = splitStr[2];
// final String appKey = splitStr[3];
} else if (httpRequest.getParameter("appkey") != null) {
appkey = httpRequest.getParameter("appkey");
userkey = httpRequest.getParameter("appuser");
StringBuilder hashBuilder = new StringBuilder();
hashBuilder.append(JaasBeanLocal.REST_PREFIX);
hashBuilder.append(appkey).append(":");
hashBuilder.append(userkey).append(":");
hashBuilder.append(httpRequest.getParameter("appstamp")).append(":");
hashBuilder.append(httpRequest.getParameter("appmac")).append(":");
hashBuilder.append(httpRequest.getPathInfo());
restAuthStr = hashBuilder.toString();
} else {
// If no rest authentication is even tried, allow, anon login
return true;
}
boolean ret = true;
String domain = parseHostname(httpRequest);
try { try {
if (restAuthStr == null) {
throw new ServletException("No auth data"); AuthHelperBeanLocal.RestAuthResponse creds = authHelper.parseRestAuthCredentials(httpRequest);
} if (creds == null) {
//final String username = "@" + parseHostname(httpRequest); return true;
String userLogin = apibean.findUsernameForApikey(appkey, userkey, domain);
if (userLogin != null) {
httpRequest.login(userLogin + '@' + domain, restAuthStr);
} }
httpRequest.login(creds.username, creds.password);
Principal p = httpRequest.getUserPrincipal(); Principal p = httpRequest.getUserPrincipal();
logger.warn("Logged in with rest:{}, ", (p == null) ? null : p.getName()); logger.warn("Logged in with rest: {}, ", (p == null) ? null : p.getName());
} catch (ServletException loginEx) { } catch (ServletException loginEx) {
ret = false;
logger.info("Rest api authentication failed for path " + httpRequest.getPathInfo() + " " logger.info("Rest api authentication failed for path " + httpRequest.getPathInfo() + " "
+ httpRequest.getParameterMap().toString(), loginEx); + httpRequest.getParameterMap().toString(), loginEx);
if (response instanceof HttpServletResponse) { if (response instanceof HttpServletResponse) {
HttpServletResponse httpResp = ((HttpServletResponse) response); HttpServletResponse httpResp = ((HttpServletResponse) response);
httpResp.setStatus(HttpServletResponse.SC_FORBIDDEN); httpResp.setStatus(HttpServletResponse.SC_FORBIDDEN);
} }
return false;
} }
return ret; return true;
} }
protected String parseHostname(HttpServletRequest httpRequest) {
StringBuffer url = httpRequest.getRequestURL();
String requestHostHeader = httpRequest.getHeader("host");
String hostname = "localhost";
if (requestHostHeader != null) {
hostname = requestHostHeader.split(":")[0];
}
// if proxy provides scheme in header, use it..
String scheme = httpRequest.getHeader(X_FORWARDED_PROTO);
if (scheme == null || scheme.isEmpty()) {
scheme = url.substring(0, 5).toLowerCase();
}
Principal principal = httpRequest.getUserPrincipal();
if (principal != null) {
String userDomain = UserLoginUtils.getDomainFromJaas(principal);
// If there is no logged-in user, we can and should not check userDomain against hostname
if (principal != null && !hostname.equals(userDomain)) {
logbean.sendMessage(MoyaEventType.USER_PERMISSION_VIOLATION,
"Hostname mismatch privilege escalation! User '", httpRequest.getUserPrincipal(), "' tried to change hostname from '",
userDomain, "' to '", hostname, ",");
throw new RuntimeException("Hostname mismatch! Expected: " + hostname + " but logged in as " + userDomain);
}
}
BortalLocalContextHolder.setInDevelopmentMode(developmentMode);
return hostname;
}
} }
...@@ -31,8 +31,7 @@ import java.io.IOException; ...@@ -31,8 +31,7 @@ import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.*; import java.util.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import static graphql.Scalars.*; import static graphql.Scalars.*;
import static graphql.schema.GraphQLArgument.newArgument; import static graphql.schema.GraphQLArgument.newArgument;
...@@ -577,8 +576,8 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -577,8 +576,8 @@ public class MoyaGraphQLServlet extends HttpServlet {
private GraphQLFieldDefinition getSingleUserQuery(GraphQLBuilder builder) { private GraphQLFieldDefinition getSingleUserQuery(GraphQLBuilder builder) {
return newFieldDefinition() return newFieldDefinition()
.name("user") .name("user")
.argument(newArgument().name("id").type(GraphQLInt).description("Id of the global user object. If id is 0, currently logged in user is used")) .argument(newArgument().name("id").defaultValue(null).type(GraphQLInt).description("Id of the global user object. If id is 0, currently logged in user is used"))
.type(builder.typeFor(User.class)) .type(builder.typeFor(EventUser.class))
.dataFetcher(environment -> { .dataFetcher(environment -> {
Integer id = environment.getArgument("userId"); Integer id = environment.getArgument("userId");
EventUser user; EventUser user;
...@@ -590,7 +589,7 @@ public class MoyaGraphQLServlet extends HttpServlet { ...@@ -590,7 +589,7 @@ public class MoyaGraphQLServlet extends HttpServlet {
if (user == null) { if (user == null) {
throw new NullPointerException("User not found with id " + id); throw new NullPointerException("User not found with id " + id);
} }
return user.getUser(); return user;
}).build(); }).build();
} }
......
...@@ -26,6 +26,7 @@ import javax.ejb.EJB; ...@@ -26,6 +26,7 @@ import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.Part; import javax.servlet.http.Part;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue; import javax.ws.rs.DefaultValue;
...@@ -43,6 +44,7 @@ import javax.ws.rs.core.Response; ...@@ -43,6 +44,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import fi.codecrew.moya.beans.auth.AuthHelperBeanLocal;
import fi.codecrew.moya.model.*; import fi.codecrew.moya.model.*;
import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
...@@ -105,6 +107,8 @@ public class UserRestView { ...@@ -105,6 +107,8 @@ public class UserRestView {
@EJB @EJB
private PlaceBeanLocal placebean; private PlaceBeanLocal placebean;
@EJB
private AuthHelperBeanLocal authHelperBean;
@POST @POST
@Path("/giveplace/{placeId}") @Path("/giveplace/{placeId}")
...@@ -222,12 +226,11 @@ public class UserRestView { ...@@ -222,12 +226,11 @@ public class UserRestView {
} }
} }
if (principal == null || principal.getName() == null || !principal.getName().equals(username)) { if (principal == null || principal.getName() == null || !principal.getName().equals(username)) {
String requestHostHeader = servletRequest.getHeader("host");
String hostname = null; String hostname = authHelperBean.parseHostname(servletRequest);
if (requestHostHeader != null) { HttpSession session = servletRequest.getSession(true);
hostname = requestHostHeader.split(":")[0]; logger.warn("Session Id {}", session.getId());
}
servletRequest.getSession(true);
servletRequest.login(username + '@' + hostname, password); servletRequest.login(username + '@' + hostname, password);
} }
} catch (ServletException e) { } catch (ServletException e) {
......
...@@ -73,7 +73,7 @@ public class ApiAppRestViewV1 { ...@@ -73,7 +73,7 @@ public class ApiAppRestViewV1 {
return Response.ok(ret).build(); return Response.ok(ret).build();
} catch (ServletException e) { } catch (ServletException e) {
logger.warn("Error logging in while creating ApiApplication instance"); logger.warn("Error logging in while creating ApiApplication instance",e);
return Response.serverError().entity(e.getCause()).build(); return Response.serverError().entity(e.getCause()).build();
} }
} }
......
...@@ -2,6 +2,7 @@ package fi.codecrew.moya.rest.appconfig.v1; ...@@ -2,6 +2,7 @@ package fi.codecrew.moya.rest.appconfig.v1;
import fi.codecrew.moya.beans.EventBeanLocal; import fi.codecrew.moya.beans.EventBeanLocal;
import fi.codecrew.moya.beans.PermissionBeanLocal; import fi.codecrew.moya.beans.PermissionBeanLocal;
import fi.codecrew.moya.beans.auth.AuthHelperBeanLocal;
import fi.codecrew.moya.rest.PojoUtils; import fi.codecrew.moya.rest.PojoUtils;
import fi.codecrew.moya.rest.pojo.appconfig.v1.EventRoot; import fi.codecrew.moya.rest.pojo.appconfig.v1.EventRoot;
import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.OpenAPIDefinition;
...@@ -36,7 +37,7 @@ import java.util.Base64; ...@@ -36,7 +37,7 @@ import java.util.Base64;
@Consumes({MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON + "; charset=UTF-8"}) @Produces({MediaType.APPLICATION_JSON + "; charset=UTF-8"})
@OpenAPIDefinition( @OpenAPIDefinition(
info = @Info(title = "Event information for application")) info = @Info(title = "Event information for application"))
public class EventInfoV1 { public class EventInfoV1 {
private static final Logger logger = LoggerFactory.getLogger(EventInfoV1.class); private static final Logger logger = LoggerFactory.getLogger(EventInfoV1.class);
...@@ -52,6 +53,9 @@ public class EventInfoV1 { ...@@ -52,6 +53,9 @@ public class EventInfoV1 {
@EJB @EJB
private EventBeanLocal eventBean; private EventBeanLocal eventBean;
@EJB
private AuthHelperBeanLocal authHelperBean;
@GET @GET
@Path("/current") @Path("/current")
public Response getCurrentEventInfo() { public Response getCurrentEventInfo() {
...@@ -63,34 +67,50 @@ public class EventInfoV1 { ...@@ -63,34 +67,50 @@ public class EventInfoV1 {
public Response getEventsForUser() { public Response getEventsForUser() {
try { try {
String authHeader = servletRequest.getHeader(AUTH_HEADER); if (!permissionBean.isLoggedIn()) {
if (authHeader == null || !authHeader.startsWith(AUTH_PREFIX)) {
return Response.status(Response.Status.FORBIDDEN).entity("No basic auth provided").build();
}
String authStr = new String(Base64.getDecoder().decode(authHeader.substring(AUTH_PREFIX.length())));
String[] splitAuth = authStr.split(":", 2);
Principal principal = servletRequest.getUserPrincipal(); AuthHelperBeanLocal.RestAuthResponse authCredentials = authHelperBean.parseRestAuthCredentials(servletRequest);
// ensure logged out user String password;
if (principal != null && principal.getName() != null) { String username;
servletRequest.logout(); if (authCredentials != null) {
} username = authCredentials.username;
String domain = servletRequest.getHeader("host"); password = authCredentials.password;
} else {
String authHeader = servletRequest.getHeader(AUTH_HEADER);
servletRequest.getSession(true); if (authHeader == null || !authHeader.startsWith(AUTH_PREFIX)) {
servletRequest.login(splitAuth[0] + "@" + domain, splitAuth[1]); return Response.status(Response.Status.FORBIDDEN).entity("No basic auth provided").build();
}
String authStr = new String(Base64.getDecoder().decode(authHeader.substring(AUTH_PREFIX.length())));
String[] splitAuth = authStr.split(":", 2);
String domain = authHelperBean.parseHostname(servletRequest);
username = splitAuth[0] + "@" + domain;
password = splitAuth[1];
}
Principal principal = servletRequest.getUserPrincipal();
// ensure logged out user
if (principal != null && principal.getName() != null) {
servletRequest.logout();
}
servletRequest.getSession(true);
servletRequest.login(username, password);
}
return Response.ok(PojoUtils.parseEvents(eventBean.findAllEventsForCurrentUser())).build(); return Response.ok(PojoUtils.parseEvents(eventBean.findAllEventsForCurrentUser())).build();
} catch (ServletException e) { } catch (ServletException e) {
logger.warn("Error logging in while creating ApiApplication instance"); logger.warn("Error logging in while fetching events", e);
return Response.serverError().entity(e.getCause()).build(); return Response.serverError().entity(e.getCause()).build();
} }
} }
@GET @GET
@Path("/listevents/") @Path("/listevents/")
@Operation(description = "Get all events for current user", responses = { @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = EventRoot.class))) }) @Operation(description = "Get all events for current user", responses = {@ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = EventRoot.class)))})
public Response getEventsForCurrentUser() { public Response getEventsForCurrentUser() {
if (permissionBean.getCurrentUser().isAnonymous()) { if (permissionBean.getCurrentUser().isAnonymous()) {
......
...@@ -9,6 +9,7 @@ import org.apache.commons.fileupload.FileItemStream; ...@@ -9,6 +9,7 @@ import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams; import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -22,6 +23,15 @@ import javax.servlet.http.HttpServletResponse; ...@@ -22,6 +23,15 @@ import javax.servlet.http.HttpServletResponse;
import java.io.*; import java.io.*;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@WebServlet(value = "/EntryFile/*") @WebServlet(value = "/EntryFile/*")
public class CompoEntryFileServlet extends HttpServlet { public class CompoEntryFileServlet extends HttpServlet {
...@@ -42,41 +52,123 @@ public class CompoEntryFileServlet extends HttpServlet { ...@@ -42,41 +52,123 @@ public class CompoEntryFileServlet extends HttpServlet {
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int fileId = Integer.parseUnsignedInt(req.getPathInfo().substring(1)); if (req.getPathInfo().startsWith("/compozip/")) {
int compoid = Integer.parseUnsignedInt(req.getPathInfo().replace("/compozip/", ""));
CompoEntryFile entry = votebean.findEntryFile(fileId); getCompoEntriesZip(req, resp, compoid);
if (entry == null) { } else {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND); int fileId = Integer.parseUnsignedInt(req.getPathInfo().substring(1));
resp.getWriter().write("Entity not found"); getEntryFile(req, resp, fileId);
return; return;
} }
}
private static String formatDateteime(Date d) {
return Instant
.ofEpochMilli(d.getTime())
.atZone(ZoneId.systemDefault())
.toOffsetDateTime()
.format(DateTimeFormatter.ISO_INSTANT);
}
public static String trimFilename(Object ... parts) {
StringBuilder sb = new StringBuilder();
for(Object i: parts){
sb.append(i.toString());
}
return sb.toString().trim().replaceAll("[^0-9a-zA-Z._-]","_");
}
private void getCompoEntriesZip(HttpServletRequest req, HttpServletResponse resp, int compoid) throws IOException {
Compo compo = votebean.getCompoById(compoid);
File file = new File(new File(SystemProperty.MOYA_COMPOFILE_DIR.getValueOrDefault()), entry.getFilepath()); resp.setContentType("application/zip");
resp.setContentType(entry.getMimeType()); resp.setHeader("Content-Disposition", "filename=\"" + trimFilename("compofiles_", compo.getId(), "_",compo.getName(),OffsetDateTime.now().format(DateTimeFormatter.ISO_INSTANT)) + ".zip\"");
resp.setHeader("Content-Disposition", "filename=\"" + entry.getFileName() + "\"");
resp.setContentLength((int) file.length()); String filepattern = req.getParameter("filenamepattern");
if (filepattern == null || filepattern.isEmpty()) {
filepattern = CompoEntryFile.DEFAULT_FILENAMEPATTERN;
}
ZipOutputStream zos = new ZipOutputStream(resp.getOutputStream());
byte[] buff = new byte[1024 * 8];
for (CompoEntry e : compo.getCompoEntries()) {
for (CompoEntryFileType ft : e.getFiletypes()) {
if (ft.getCurrentFile() == null) {
continue;
}
CompoEntryFile file = ft.getCurrentFile();
ZipEntry zipEntry = new ZipEntry(file.getFilenameFromPattern(filepattern));
zos.putNextEntry(zipEntry);
FileInputStream fstream = new FileInputStream(new File(getCompofileDir(), file.getFilepath()));
try {
copyStream(fstream, zos, buff);
} finally {
fstream.close();
}
}
}
zos.close();
}
FileInputStream inStream = new FileInputStream(file);
ServletOutputStream oStream = resp.getOutputStream();
public File getCompofileDir() {
return new File(SystemProperty.MOYA_COMPOFILE_DIR.getValueOrDefault());
}
final byte[] buff = new byte[8192]; public static void copyStream(InputStream istream, OutputStream ostream, byte[] buff) throws IOException {
while (true) { while (true) {
int len = inStream.read(buff); int len = istream.read(buff);
if (len < 1) { if (len < 1) {
break; break;
} }
oStream.write(buff, 0, len); ostream.write(buff, 0, len);
}
}
private void getEntryFile(HttpServletRequest req, HttpServletResponse resp, int fileId) throws IOException {
Set<Closeable> closeables = new HashSet<>();
try {
CompoEntryFile entry = votebean.findEntryFile(fileId);
if (entry == null) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
resp.getWriter().write("Entity not found");
return;
}
File file = new File(getCompofileDir(), entry.getFilepath());
resp.setContentType(entry.getMimeType());
resp.setHeader("Content-Disposition", "filename=\"" + entry.getFilenameFromPattern(entry.DEFAULT_FILENAMEPATTERN) + "\"");
resp.setContentLength((int) file.length());
FileInputStream inStream = new FileInputStream(file);
ServletOutputStream oStream = resp.getOutputStream();
closeables.add(inStream);
closeables.add(oStream);
final byte[] buff = new byte[8192];
copyStream(inStream, oStream, buff);
} finally {
for (Closeable c : closeables) {
try {
c.close();
} catch (Exception e) {
logger.warn("Error closing stream", e);
// Do not error on closing stream
}
}
} }
oStream.close();
} }
@Override @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
File dir = new File(SystemProperty.MOYA_COMPOFILE_DIR.getValueOrDefault()); File dir = new File(SystemProperty.MOYA_COMPOFILE_DIR.getValueOrDefault());
if (!dir.isDirectory()) { if (!dir.isDirectory()) {
dir.mkdirs(); if (!dir.mkdirs() && !dir.isDirectory()) {
throw new IOException("Unable to create destination directory: '" + dir.getAbsolutePath() + "'");
}
} }
boolean isMultipart = ServletFileUpload.isMultipartContent(req); boolean isMultipart = ServletFileUpload.isMultipartContent(req);
...@@ -103,49 +195,56 @@ public class CompoEntryFileServlet extends HttpServlet { ...@@ -103,49 +195,56 @@ public class CompoEntryFileServlet extends HttpServlet {
FileItemStream item = iter.next(); FileItemStream item = iter.next();
String name = item.getFieldName(); String name = item.getFieldName();
InputStream stream = item.openStream(); InputStream stream = item.openStream();
try {
if (item.isFormField()) {
final String data = Streams.asString(stream); if (item.isFormField()) {
logger.warn("Streaming upload form field " + name + " with value " + data + " detected."); final String data = Streams.asString(stream);
switch (name) { logger.warn("Streaming upload form field " + name + " with value " + data + " detected.");
case "entryId": { switch (name) {
entryId = Integer.parseInt(data); case "entryId": {
break; entryId = Integer.parseInt(data);
break;
}
case "typeId": {
typeId = Integer.parseInt(data);
break;
}
} }
case "typeId": { } else {
typeId = Integer.parseInt(data); logger.warn("File field " + name + " with file name " + item.getName() + " detected." + item.getContentType());
break;
MessageDigest algo = MessageDigest.getInstance("SHA");
FileOutputStream ostream = new FileOutputStream(tmpFile);
try {
while (true) {
int len = stream.read(buff);
if (len < 1) {
break;
}
ostream.write(buff, 0, len);
algo.update(buff, 0, len);
}
String shasum = new String(Hex.encodeHex(algo.digest())).toLowerCase();
logger.warn("File shasum " + shasum);
entryfile.setFileName(item.getName());
entryfile.setMimeType(item.getContentType());
entryfile.setHash(shasum);
} finally {
ostream.close();
} }
} }
} else { } finally {
logger.warn("File field " + name + " with file name " + item.getName() + " detected." + item.getContentType()); stream.close();
MessageDigest algo = MessageDigest.getInstance("SHA");
FileOutputStream ostream = new FileOutputStream(tmpFile);
while (true) {
int len = stream.read(buff);
if (len < 1) {
break;
}
ostream.write(buff, 0, len);
algo.update(buff, 0, len);
}
String shasum = new String(Hex.encodeHex(algo.digest())).toLowerCase();
logger.warn("File shasum " + shasum);
ostream.close();
entryfile.setFileName(item.getName());
entryfile.setMimeType(item.getContentType());
entryfile.setHash(shasum);
} }
stream.close();
} }
CompoEntryFileType ft = votebean.findEntryFileType(entryId, typeId); CompoEntryFileType ft = votebean.findEntryFileType(entryId, typeId);
String ftPath = mkEntryFiletypePath(ft); String ftPath = mkEntryFiletypePath(ft);
File filetypepath = new File(dir, ftPath); File filetypepath = new File(dir, ftPath);
if (!filetypepath.isDirectory()) { if (!filetypepath.isDirectory()) {
filetypepath.mkdirs(); if (!filetypepath.mkdirs() && !filetypepath.isDirectory()) {
throw new IOException("Could not create directory: " + filetypepath.getAbsolutePath());
}
} }
String filePath = ftPath + mkPath(entryfile.getUploaded().getTime() + "_" + entryfile.getFileName()) + ".dat"; String filePath = ftPath + mkPath(entryfile.getUploaded().getTime() + "_" + entryfile.getFileName()) + ".dat";
logger.warn("Target file: {}, ftpath {}", filePath, ftPath); logger.warn("Target file: {}, ftpath {}", filePath, ftPath);
...@@ -162,11 +261,14 @@ public class CompoEntryFileServlet extends HttpServlet { ...@@ -162,11 +261,14 @@ public class CompoEntryFileServlet extends HttpServlet {
} catch (FileUploadException e) { } catch (FileUploadException e) {
logger.warn("Error uploading streaming file", e); logger.warn("Error uploading streaming file", e);
throw new IOException("Error uploading file");
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
logger.warn("Error calculating checksum", e); logger.warn("Error calculating checksum", e);
throw new IOException("Error uploading file");
} }
} }
/*
String asString(InputStream stream) throws IOException { String asString(InputStream stream) throws IOException {
final byte[] buff = new byte[100]; final byte[] buff = new byte[100];
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
...@@ -180,7 +282,7 @@ public class CompoEntryFileServlet extends HttpServlet { ...@@ -180,7 +282,7 @@ public class CompoEntryFileServlet extends HttpServlet {
} }
return baos.toString(); return baos.toString();
} }
*/
public static void main(String[] asd) { public static void main(String[] asd) {
logger.warn("asda {}", mkPath("Foo", "[]{}äöherp", "1239KKKKK")); logger.warn("asda {}", mkPath("Foo", "[]{}äöherp", "1239KKKKK"));
} }
......
...@@ -48,8 +48,8 @@ ...@@ -48,8 +48,8 @@
<rewriteservlet.version>3.4.2.Final</rewriteservlet.version> <rewriteservlet.version>3.4.2.Final</rewriteservlet.version>
<iudex.standalone>1.0.23</iudex.standalone> <iudex.standalone>1.0.23</iudex.standalone>
<js.node.version>v8.6.0</js.node.version> <js.node.version>v8.11.3</js.node.version>
<js.npm.version>5.6.0</js.npm.version> <js.npm.version>6.3.0</js.npm.version>
<eirslett.frontend.version>1.4</eirslett.frontend.version> <eirslett.frontend.version>1.4</eirslett.frontend.version>
<payara.version>4.1.2.181</payara.version> <payara.version>4.1.2.181</payara.version>
</properties> </properties>
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!