UserServiceImpl.java

/*
 * Copyright 2019 Global Crop Diversity Trust
 *
 * 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.gringlobal.compatibility.service.impl;

import java.util.Date;
import java.util.List;

import lombok.extern.slf4j.Slf4j;
import org.genesys.blocks.security.NoUserFoundException;
import org.genesys.blocks.security.UserException;
import org.gringlobal.compatibility.service.UserService;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.QSite;
import org.gringlobal.model.QSysLang;
import org.gringlobal.model.QSysUser;
import org.gringlobal.model.SysLang;
import org.gringlobal.model.SysUser;
import org.gringlobal.persistence.CooperatorRepository;
import org.gringlobal.persistence.SysLangRepository;
import org.gringlobal.persistence.SysUserRepository;
import org.gringlobal.soap.Datatable;
import org.gringlobal.soap.Datatable.HasChanges;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
@DependsOn("applicationStartup")
@Slf4j
public class UserServiceImpl implements UserService {

	@Autowired
	private SysUserRepository userRepository;

	@Autowired
	private CooperatorRepository cooperatorRepository;

	@Autowired
	private SysLangRepository langRepository;

	@Autowired
	private org.gringlobal.service.UserService userService;

	@Override
	@PreAuthorize("hasAuthority('GROUP_ADMINS') || hasAuthority('GROUP_CTUSERS')")
	public Datatable login(final String username) throws AuthenticationException {

		SysUser sysUser = sysUserFromAuthentication();

		if (!sysUser.getUsername().equalsIgnoreCase(username)) {
			throw new PreAuthenticatedCredentialsNotFoundException("Username mismatch");
		}

		try {
			sysUser = userService.loadSysUser(sysUser.getId());
		} catch (NoUserFoundException e) {
			throw new PreAuthenticatedCredentialsNotFoundException("No such user");
		}

		/*@formatter:off*/
		/*
		<sys_user_id>1</sys_user_id>
		<user_name>administrator</user_name>
		<is_enabled>Y</is_enabled>
		<cooperator_id>48</cooperator_id>
		<created_date>2009-01-09T00:00:00-08:00</created_date>
		<created_by>1</created_by>
		<modified_date>2024-12-08T17:16:49-08:00</modified_date>
		<modified_by>48</modified_by>
		<owned_date>2009-01-09T00:00:00-08:00</owned_date>
		<owned_by>1</owned_by>
		<sys_lang_id>1</sys_lang_id>
		<site_id>1</site_id>
		<site>SYS</site>
		<groups>admins	allusers	ctusers	webtools</groups>
		<login_token>Qq4ycIMSCFwSUH4UjyvqQjWMKiDPx7SzGsKTTeQNKj/pLE4vmovqrvN3Xou/qGJtj549oAj7DYYccPHTUyUc1NoXFQ6GuPQKZ2O+C98K0IB4MeJsiEeLJE/zwPw1+q9w</login_token>
		<warning />
		*/
		/*@formatter:on*/

		final Datatable result = new Datatable("validate_login", List.of(
			new Datatable.Column("sys_user_id", QSysUser.sysUser.id.getType()),
			new Datatable.Column("user_name", QSysUser.sysUser.username.getType()),
			new Datatable.Column("is_enabled", QSysUser.sysUser.isEnabled.getType()),
			new Datatable.Column("cooperator_id", QSysUser.sysUser.cooperator().id.getType()),
			new Datatable.Column("created_date", Date.class),
			new Datatable.Column("created_by", Long.class),
			new Datatable.Column("modified_date", Date.class),
			new Datatable.Column("owned_date", Date.class),
			new Datatable.Column("owned_by", Long.class),
			new Datatable.Column("sys_lang_id", QSysLang.sysLang.id.getType()),
			new Datatable.Column("site_id", QSite.site.id.getType()),
			new Datatable.Column("site", QSite.site.siteShortName.getType()),
			new Datatable.Column("groups", String.class),
			new Datatable.Column("login_token", String.class),
			new Datatable.Column("warning", String.class)
		));

		Cooperator userCooperator = sysUser.getCooperator();
		if (userCooperator == null) {
			throw new RuntimeException("User has no Cooperator record.");
		}
		String userGroups = sysUser.getGroupMaps().stream().map(group -> group.getSysGroup().getAuthority()).reduce("", (a, b) -> a += "\t" + b).strip();
		result.addRow(HasChanges.original, sysUser.getId(), sysUser.getUsername(), sysUser.getIsEnabled(), userCooperator.getId(), sysUser.getCreatedDate(), null,
			sysUser.getLastModifiedDate(), null, null, userCooperator.getSysLang().getId(),
			userCooperator.getSite() == null ? null : userCooperator.getSite().getId(),
			userCooperator.getSite() == null ? null : userCooperator.getSite().getSiteShortName(),
			userGroups,
			// Token
			"THIS-IS-NOT-A-TOKEN",
			// Warning
			null);

		return result;
	}

	@Override
	@PreAuthorize("hasAuthority('GROUP_ADMINS') || hasAuthority('GROUP_CTUSERS')")
	@Transactional
	public Datatable setPassword(final String targetUserName, final String newPassword) throws UserException {

		final SysUser sysTargetUser = userRepository.findOne(QSysUser.sysUser.username.eq(targetUserName)).orElse(null);

		if (sysTargetUser != null) {
			userService.setPassword(sysTargetUser, newPassword);

			log.warn("Updated password for {} to {} hashed={}", sysTargetUser.getUsername(), /* newPassword */"*", sysTargetUser.getPassword());
		}

		return new Datatable();
	}

	@Override
	@PreAuthorize("hasAuthority('GROUP_ADMINS') || hasAuthority('GROUP_CTUSERS')")
	@Transactional
	public Datatable setLanguage(final long newLanguageID) {
		final SysUser sysUser = sysUserFromAuthentication();

		final Cooperator cooperator = cooperatorRepository.findById(sysUser.getCooperator().getId()).orElse(sysUser.getCooperator());

		final SysLang sysLang = langRepository.findById(newLanguageID).orElse(null);

		cooperator.setSysLang(sysLang);

		return new Datatable();
	}

	static SysUser sysUserFromAuthentication() {
		SysUser sysUser = null;

		final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		if (authentication != null) {
			final Object principal = authentication.getPrincipal();
			if (principal instanceof SysUser) {
				sysUser = (SysUser) principal;
			} else {
				log.warn("Principal {} is not SysUser, but type {}. Auth of type {}", principal, principal.getClass(), authentication.getClass());
				throw new PreAuthenticatedCredentialsNotFoundException("Principal is not SysUser");
			}
		}
		return sysUser;
	}

}