<commit_before>/*
 * Copyright (c) 2015 Kagilum SAS
 *
 * This file is part of iceScrum.
 *
 * iceScrum 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.
 *
 * iceScrum 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * <NAME> (<EMAIL>)
 * <NAME> (<EMAIL>)
 *
 */
package org.icescrum.web.presentation.api

import grails.converters.JSON
import grails.plugin.springsecurity.annotation.Secured
import org.apache.commons.io.FilenameUtils
import org.apache.commons.validator.GenericValidator
import org.hibernate.ObjectNotFoundException
import org.icescrum.components.FileUploadInfo
import org.icescrum.components.UtilsWebComponents
import org.icescrum.core.domain.Activity
import org.icescrum.core.domain.Invitation
import org.icescrum.core.domain.Project
import org.icescrum.core.domain.User
import org.icescrum.core.domain.preferences.UserPreferences
import org.icescrum.core.error.BusinessException
import org.icescrum.core.error.ControllerErrorHandler
import org.icescrum.core.support.ApplicationSupport
import org.springframework.mail.MailException
import org.springframework.security.acls.domain.BasePermission

class UserController implements ControllerErrorHandler {

    def userService
    def securityService
    def grailsApplication
    def springSecurityService

    @Secured(["hasRole('ROLE_ADMIN')"])
    def index(String term, String filter, Boolean paginate, Integer count, Integer page, String sorting, String order) {
        def options = [cache: true]
        if (paginate) {
            options.offset = page ? (page - 1) * count : 0
            options.max = count ?: 10
            options.sort = sorting ?: 'firstName'
            options.order = order ?: 'asc'
        }
        def users
        def userCount
        if (filter == "enabled") {
            users = term ? User.findUsersLikeAndEnabled(false, term, true, options) : User.findAllByEnabled(true, options)
            if (paginate) {
                userCount = term ? User.countUsersLikeAndEnabled(false, term, true, [cache: true]) : User.countByEnabled(true)
            }
        } else if (filter == "disabled") {
            users = term ? User.findUsersLikeAndEnabled(false, term, false, options) : User.findAllByEnabled(false, options)
            if (paginate) {
                userCount = term ? User.countUsersLikeAndEnabled(false, term, false, [cache: true]) : User.countByEnabled(false)
            }
        } else {
            users = term ? User.findUsersLike(term, false, true, options) : User.list(options)
            if (paginate) {
                userCount = term ? User.countUsersLike(false, term, [cache: true]) : User.count()
            }
        }
        request.marshaller = [user: [include: ['preferences']]]
        def returnData = paginate ? [users: users, count: userCount] : users
        render(status: 200, contentType: 'application/json', text: returnData as JSON)
    }

    @Secured("isAuthenticatedWeb() || hasAnyScopeOauth2('user', 'user:read')")
    def show(long id) {
        User user = User.withUser(id)
        if (user.id != springSecurityService.principal.id && !request.admin) {
            render(status: 403)
            return
        }
        request.marshaller = [user: [include: ['preferences']]]
        render(status: 200, contentType: 'application/json', text: user as JSON)
    }

    @Secured(["!isAuthenticated() or hasRole('ROLE_ADMIN')"])
    def save() {
        def userParams = params.user
        if (!userParams) {
            returnError(code: 'todo.is.ui.no.data')
            return
        }
        if (!request.restAPI && userParams.confirmPassword != userParams.password) {
            // Cannot be executed inside withFormat closure because return will return only from closure and code below will still be executed
            redirect(action: 'register', model: [error: message(code: 'is.user.error.password.check')])
            return
        }
        User user = new User()
        try {
            User.withTransaction {
                def propertiesToBind = ['username', 'firstName', 'lastName', 'email', 'password']
                if (request.admin) {
                    propertiesToBind << 'accountExternal'
                }
                bindData(user, userParams, [include: propertiesToBind])
                user.preferences = new UserPreferences()
                bindData(user.preferences, userParams.preferences, [include: ['language', 'activity']])
                if (userParams.preferences?.containsKey('colorScheme')) {
                    user.preferences.colorScheme = userParams.preferences.colorScheme == 'null' ? null : userParams.preferences.colorScheme
                }
                userService.save(user, userParams.token)
            }
        } catch (RuntimeException e) {
            if (!request.restAPI) {
                render(status: 200, view: 'register', model: [user: user])
                return
            } else {
                returnError(exception: e)
            }
        }
        if (request.restAPI) {
            render(status: 201, contentType: 'application/json', text: user as JSON)
        } else {
            redirect(status: 200, action: 'auth', controller: 'login', params: [username: user.username])
        }
    }

    @Secured("isAuthenticatedWeb() || hasAnyScopeOauth2('user', 'user:write')")
    def update(long id) {
        User user = User.withUser(id)
        if (user.id != springSecurityService.principal.id && !request.admin) {
            render(status: 403)
            return
        }
        Map props = [:]
        if (params.flowIdentifier) {
            User.withTransaction {
                def endOfUpload = { FileUploadInfo uploadInfo ->
                    def uploadedAvatar = new File(uploadInfo.filePath)
                    props.avatar = uploadedAvatar.canonicalPath
                    props.scale = true
                    userService.update(user, props)
                    render(status: 200, contentType: 'application/json', text: user as JSON)
                }
                UtilsWebComponents.handleUpload.delegate = this
                UtilsWebComponents.handleUpload(request, params, endOfUpload)
            }
            return;
        }
        if (!params.user) {
            returnError(code: 'todo.is.ui.no.data')
            return
        }
        if (!request.restAPI && (params.user.confirmPassword || params.user.password != "") && (params.user.confirmPassword != params.user.password)) {
            returnError(code: 'is.user.error.password.check')
            return
        }
        User.withTransaction {
            if (request.admin && params.user.username != user.username) {
                user.username = params.user.username
            }
            def propertiesToBind = ['firstName', 'lastName', 'email', 'notes']
            if (request.admin) {
                propertiesToBind << 'accountExternal'
            }
            bindData(user, params.user, [include: propertiesToBind])
            if (params.user.preferences) {
                bindData(user.preferences, params.user.preferences, [include: ['language', 'filterTask', 'activity', 'displayWhatsNew', 'displayReleaseNotes']])
                if (params.user.preferences.containsKey('colorScheme')) {
                    user.preferences.colorScheme = params.user.preferences.colorScheme == 'null' ? null : params.user.preferences.colorScheme<commit_msg>T1809-0 email validation<commit_after>/*
 * Copyright (c) 2015 <NAME>
 *
 * This file is part of iceScrum.
 *
 * iceScrum 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.
 *
 * iceScrum 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * <NAME> (<EMAIL>)
 * <NAME> (<EMAIL>)
 *
 */
package org.icescrum.web.presentation.api

import grails.converters.JSON
import grails.plugin.springsecurity.annotation.Secured
import org.apache.commons.io.FilenameUtils
import org.apache.commons.validator.GenericValidator
import org.hibernate.ObjectNotFoundException
import org.icescrum.components.FileUploadInfo
import org.icescrum.components.UtilsWebComponents
import org.icescrum.core.domain.Activity
import org.icescrum.core.domain.Invitation
import org.icescrum.core.domain.Project
import org.icescrum.core.domain.User
import org.icescrum.core.domain.preferences.UserPreferences
import org.icescrum.core.error.BusinessException
import org.icescrum.core.error.ControllerErrorHandler
import org.icescrum.core.support.ApplicationSupport
import org.springframework.mail.MailException
import org.springframework.security.acls.domain.BasePermission

class UserController implements ControllerErrorHandler {

    def userService
    def securityService
    def grailsApplication
    def springSecurityService

    @Secured(["hasRole('ROLE_ADMIN')"])
    def index(String term, String filter, Boolean paginate, Integer count, Integer page, String sorting, String order) {
        def options = [cache: true]
        if (paginate) {
            options.offset = page ? (page - 1) * count : 0
            options.max = count ?: 10
            options.sort = sorting ?: 'firstName'
            options.order = order ?: 'asc'
        }
        def users
        def userCount
        if (filter == "enabled") {
            users = term ? User.findUsersLikeAndEnabled(false, term, true, options) : User.findAllByEnabled(true, options)
            if (paginate) {
                userCount = term ? User.countUsersLikeAndEnabled(false, term, true, [cache: true]) : User.countByEnabled(true)
            }
        } else if (filter == "disabled") {
            users = term ? User.findUsersLikeAndEnabled(false, term, false, options) : User.findAllByEnabled(false, options)
            if (paginate) {
                userCount = term ? User.countUsersLikeAndEnabled(false, term, false, [cache: true]) : User.countByEnabled(false)
            }
        } else {
            users = term ? User.findUsersLike(term, false, true, options) : User.list(options)
            if (paginate) {
                userCount = term ? User.countUsersLike(false, term, [cache: true]) : User.count()
            }
        }
        request.marshaller = [user: [include: ['preferences']]]
        def returnData = paginate ? [users: users, count: userCount] : users
        render(status: 200, contentType: 'application/json', text: returnData as JSON)
    }

    @Secured("isAuthenticatedWeb() || hasAnyScopeOauth2('user', 'user:read')")
    def show(long id) {
        User user = User.withUser(id)
        if (user.id != springSecurityService.principal.id && !request.admin) {
            render(status: 403)
            return
        }
        request.marshaller = [user: [include: ['preferences']]]
        render(status: 200, contentType: 'application/json', text: user as JSON)
    }

    @Secured(["!isAuthenticated() or hasRole('ROLE_ADMIN')"])
    def save() {
        def userParams = params.user
        if (!userParams) {
            returnError(code: 'todo.is.ui.no.data')
            return
        }
        if (!request.restAPI && userParams.confirmPassword != userParams.password) {
            // Cannot be executed inside withFormat closure because return will return only from closure and code below will still be executed
            redirect(action: 'register', model: [error: message(code: 'is.user.error.password.check')])
            return
        }
        User user = new User()
        try {
            User.withTransaction {
                def propertiesToBind = ['username', 'firstName', 'lastName', 'email', 'password']
                if (request.admin) {
                    propertiesToBind << 'accountExternal'
                }
                bindData(user, userParams, [include: propertiesToBind])
                user.preferences = new UserPreferences()
                bindData(user.preferences, userParams.preferences, [include: ['language', 'activity', 'needsEmailValidation']])
                if (userParams.preferences?.containsKey('colorScheme')) {
                    user.preferences.colorScheme = userParams.preferences.colorScheme == 'null' ? null : userParams.preferences.colorScheme
                }
                userService.save(user, userParams.token)
            }
        } catch (RuntimeException e) {
            if (!request.restAPI) {
                render(status: 200, view: 'register', model: [user: user])
                return
            } else {
                returnError(exception: e)
            }
        }
        if (request.restAPI) {
            render(status: 201, contentType: 'application/json', text: user as JSON)
        } else {
            redirect(status: 200, action: 'auth', controller: 'login', params: [username: user.username])
        }
    }

    @Secured("isAuthenticatedWeb() || hasAnyScopeOauth2('user', 'user:write')")
    def update(long id) {
        User user = User.withUser(id)
        if (user.id != springSecurityService.principal.id && !request.admin) {
            render(status: 403)
            return
        }
        Map props = [:]
        if (params.flowIdentifier) {
            User.withTransaction {
                def endOfUpload = { FileUploadInfo uploadInfo ->
                    def uploadedAvatar = new File(uploadInfo.filePath)
                    props.avatar = uploadedAvatar.canonicalPath
                    props.scale = true
                    userService.update(user, props)
                    render(status: 200, contentType: 'application/json', text: user as JSON)
                }
                UtilsWebComponents.handleUpload.delegate = this
                UtilsWebComponents.handleUpload(request, params, endOfUpload)
            }
            return;
        }
        if (!params.user) {
            returnError(code: 'todo.is.ui.no.data')
            return
        }
        if (!request.restAPI && (params.user.confirmPassword || params.user.password != "") && (params.user.confirmPassword != params.user.password)) {
            returnError(code: 'is.user.error.password.check')
            return
        }
        User.withTransaction {
            if (request.admin && params.user.username != user.username) {
                user.username = params.user.username
            }
            def propertiesToBind = ['firstName', 'lastName', 'email', 'notes']
            if (request.admin) {
                propertiesToBind << 'accountExternal'
            }
            bindData(user, params.user, [include: propertiesToBind])
            if (params.user.preferences) {
                bindData(user.preferences, params.user.preferences, [include: ['language', 'filterTask', 'activity', 'displayWhatsNew', 'displayReleaseNotes', 'needsEmailValidation']])
                if (params.user.preferences.containsKey('colorScheme')) {
                    user.preferences.colorScheme = params.user.preferences.colorScheme == 'null' ? null : params.user.preferences.colorScheme
{'fraction_non_alphanumeric': 0.10814377277868543, 'fraction_numerical': 0.004712831469146663, 'mean_word_length': 2.38689082783571, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5328403', 'n_tokens_mistral': 4330, 'n_tokens_neox': 4158, 'n_words': 1052}