/** * PODD is an OWL ontology database used for scientific project management * * Copyright (C) 2009-2013 The University Of Queensland * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see <http://www.gnu.org/licenses/>. */ package com.github.podd.resources; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Map; import java.util.Set; import org.openrdf.OpenRDFException; import org.openrdf.model.Model; import org.openrdf.model.URI; import org.openrdf.model.impl.LinkedHashModel; import org.openrdf.rio.RDFFormat; import org.openrdf.rio.Rio; import org.restlet.data.CookieSetting; import org.restlet.data.MediaType; import org.restlet.data.Status; import org.restlet.representation.ByteArrayRepresentation; import org.restlet.representation.Representation; import org.restlet.representation.Variant; import org.restlet.resource.Get; import org.restlet.resource.Post; import org.restlet.resource.ResourceException; import org.restlet.security.Role; import org.restlet.security.SecretVerifier; import org.restlet.security.User; import org.restlet.security.Verifier; import com.github.ansell.restletutils.SesameRealmConstants; import com.github.podd.restlet.PoddAction; import com.github.podd.restlet.PoddSesameRealm; import com.github.podd.restlet.PoddWebServiceApplication; import com.github.podd.restlet.RestletUtils; import com.github.podd.utils.PODD; import com.github.podd.utils.PoddUser; import com.github.podd.utils.PoddWebConstants; /** * This resource handles User password change. * * @author kutila */ public class UserPasswordResourceImpl extends AbstractUserResourceImpl { private URI changePassword(final PoddSesameRealm nextRealm, final Model model, final PoddUser changePwdUser, final boolean changeOwnPassword) { final String identifierInModel = model.filter(null, SesameRealmConstants.OAS_USERIDENTIFIER, null).objectString(); // verify user identifier in Model is same as that from the request if(!changePwdUser.getIdentifier().equals(identifierInModel)) { throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Problem with input: user identifiers don't match"); } // changing own password, verify old password. if(changeOwnPassword) { final String oldPassword = model.filter(null, PODD.PODD_USER_OLDSECRET, null).objectString(); final Verifier verifier = nextRealm.getVerifier(); if(verifier == null) { this.log.warn("Could not access Verifier to check old password"); throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Failed to access Verifier"); } if(verifier instanceof SecretVerifier) { final int verifyResult = ((SecretVerifier)verifier).verify(identifierInModel, oldPassword.toCharArray()); if(verifyResult != Verifier.RESULT_VALID) { throw new ResourceException(Status.CLIENT_ERROR_UNAUTHORIZED, "Old password is invalid."); } } else { this.log.warn("Could not access Verifier to check old password"); throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Failed to access Verifier"); } } // update sesame Realm with new password final String newPassword = model.filter(null, SesameRealmConstants.OAS_USERSECRET, null).objectString(); // this.log.info("[DEBUG] new password is [{}]", newPassword); changePwdUser.setSecret(newPassword.toCharArray()); return nextRealm.updateUser(changePwdUser); } /** * Handle password change requests */ @Post("rdf|rj|ttl") public Representation editPasswordRdf(final Representation entity, final Variant variant) throws ResourceException { this.log.info("changePasswordRdf"); final String requestedUserIdentifier = this.getUserParameter(); final PoddAction action = this.getAction(requestedUserIdentifier, PoddAction.OTHER_USER_EDIT, PoddAction.CURRENT_USER_EDIT); final boolean changeOwnPassword = (action == PoddAction.CURRENT_USER_EDIT); this.log.info("requesting change password of user: {}", requestedUserIdentifier); if(requestedUserIdentifier == null) { throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Did not specify user"); } final User user = this.getRequest().getClientInfo().getUser(); this.log.info("authenticated user: {}", user); this.checkAuthentication(action); final PoddSesameRealm nextRealm = ((PoddWebServiceApplication)this.getApplication()).getRealm(); final PoddUser poddUser = nextRealm.findUser(requestedUserIdentifier); if(poddUser == null) { throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "User not found"); } URI userUri = null; try { // - get input stream with RDF content final InputStream inputStream = entity.getStream(); final RDFFormat inputFormat = Rio.getParserFormatForMIMEType(entity.getMediaType().getName(), RDFFormat.RDFXML); final Model modifiedUserModel = Rio.parse(inputStream, "", inputFormat); userUri = this.changePassword(nextRealm, modifiedUserModel, poddUser, changeOwnPassword); } catch(IOException | OpenRDFException e) { throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "There was a problem with the input", e); } // - set Credentials Cookie to expire so that User has to login again if(changeOwnPassword) { final String cookieName = this.getPoddApplication().getPropertyUtil() .get(PoddWebConstants.PROPERTY_COOKIE_NAME, PoddWebConstants.DEF_COOKIE_NAME); final CookieSetting credentialsCookie = this.getResponse().getCookieSettings().getFirst(cookieName); if(credentialsCookie != null) { credentialsCookie.setMaxAge(0); this.getResponse().getCookieSettings().add(credentialsCookie); this.log.debug("Set max Age of Credentials Cookie to expire"); } } // - prepare response final ByteArrayOutputStream output = new ByteArrayOutputStream(8096); final RDFFormat outputFormat = Rio.getWriterFormatForMIMEType(variant.getMediaType().getName(), RDFFormat.RDFXML); try { final Model model = new LinkedHashModel(); model.add(userUri, SesameRealmConstants.OAS_USERIDENTIFIER, PODD.VF.createLiteral(poddUser.getIdentifier())); Rio.write(model, output, outputFormat); } catch(final OpenRDFException e) { throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Could not create response"); } return new ByteArrayRepresentation(output.toByteArray(), MediaType.valueOf(outputFormat.getDefaultMIMEType())); } /** * Display the HTML page for password change */ @Get(":html") public Representation getUserPasswordPageHtml(final Representation entity) throws ResourceException { this.log.info("getUserPasswordHtml"); final String requestedUserIdentifier = this.getUserParameter(); final PoddAction action = this.getAction(requestedUserIdentifier, PoddAction.OTHER_USER_EDIT, PoddAction.CURRENT_USER_EDIT); final boolean changeOwnPassword = (action == PoddAction.CURRENT_USER_EDIT); this.log.info("requesting change password of user: {}", requestedUserIdentifier); if(requestedUserIdentifier == null) { throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Did not specify user"); } final User user = this.getRequest().getClientInfo().getUser(); this.log.info("authenticated user: {}", user); // identify needed Action this.checkAuthentication(action); // completed checking authorization final Map<String, Object> dataModel = RestletUtils.getBaseDataModel(this.getRequest()); dataModel.put("contentTemplate", "editUserPwd.html.ftl"); dataModel.put("pageTitle", "Edit Password"); dataModel.put("authenticatedUserIdentifier", user.getIdentifier()); final PoddSesameRealm realm = ((PoddWebServiceApplication)this.getApplication()).getRealm(); final PoddUser poddUser = realm.findUser(requestedUserIdentifier); if(poddUser == null) { throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, "User not found."); } else { dataModel.put("requestedUser", poddUser); final Set<Role> roles = realm.findRoles(poddUser); dataModel.put("repositoryRoleList", roles); } // Output the base template, with contentTemplate from the dataModel defining the template // to use for the content in the body of the page return RestletUtils.getHtmlRepresentation( this.getPoddApplication().getPropertyUtil() .get(PoddWebConstants.PROPERTY_TEMPLATE_BASE, PoddWebConstants.DEFAULT_TEMPLATE_BASE), dataModel, MediaType.TEXT_HTML, this.getPoddApplication().getTemplateConfiguration()); } }