Skip to content

Commit

Permalink
Merge pull request #275 from micromata/Release-8.1-SNAPSHOT
Browse files Browse the repository at this point in the history
Release 8.1 snapshot
- Visibility of plugin licensesManagement is now configurable.
- Bugfix: TaskServicesRest: access checking: whole task tree were shown while editing time sheets.
- Multi selection of employees fixed: Visitorbook and Vacation entries.
  • Loading branch information
kreinhard authored Jan 30, 2025
2 parents 40c240d + fb6eec1 commit 7bfe892
Show file tree
Hide file tree
Showing 19 changed files with 262 additions and 129 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.projectforge.framework.access.OperationType;
import org.projectforge.framework.persistence.user.entities.PFUserDO;
import org.projectforge.web.WicketSupport;
import org.springframework.beans.factory.annotation.Autowired;

/**
* Every user has access to own to-do's or to-do's he's assigned to. All other users have access if the to-do is
Expand All @@ -55,7 +56,7 @@ public LicenseManagementRight()
public boolean hasAccess(final PFUserDO user, final LicenseDO obj, final LicenseDO oldObj,
final OperationType operationType)
{
return true;
return LicensePluginService.getInstance().hasAccess();
}

public boolean isLicenseKeyVisible(final PFUserDO user, final LicenseDO license)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2025 Micromata GmbH, Germany (www.micromata.com)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition 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 General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.plugins.licensemanagement

import org.projectforge.menu.builder.MenuItemDef
import org.projectforge.menu.builder.MenuItemDefId
import org.projectforge.plugins.core.AbstractPlugin
import org.projectforge.plugins.core.PluginAdminService
import org.projectforge.plugins.licensemanagement.rest.LicensePagesRest
import org.projectforge.registry.RegistryEntry
import org.projectforge.security.My2FAShortCut
import org.projectforge.web.WicketSupport
import org.projectforge.web.plugin.PluginWicketRegistrationService

/**
* @author Kai Reinhard ([email protected])
*/
class LicenseManagementPlugin : AbstractPlugin(
PluginAdminService.PLUGIN_LICENSE_MANAGEMENT_ID,
"LicenseManagementPlugin",
"For managing software licenses, keys and usage."
) {
/**
* @see org.projectforge.plugins.core.AbstractPlugin.initialize
*/
override fun initialize() {
val licenseDao = WicketSupport.get(LicenseDao::class.java)
val pluginWicketRegistrationService = WicketSupport.get(
PluginWicketRegistrationService::class.java
)
registerShortCutValues(My2FAShortCut.FINANCE_WRITE, "WRITE:license;/wa/licenseManagementEdit")
registerShortCutValues(My2FAShortCut.FINANCE_WRITE, "/wa/licenseManagement")
registerShortCutClasses(My2FAShortCut.FINANCE, LicensePagesRest::class.java)
val entry = RegistryEntry(
ID,
LicenseDao::class.java, licenseDao,
"plugins.licensemanagement"
)
// The LicenseDao is automatically available by the scripting engine!
register(entry)

// Register the web part:
pluginWicketRegistrationService.registerWeb(
ID,
LicenseListPage::class.java,
LicenseEditPage::class.java
)

val menuItemDef = MenuItemDef(
ID, "plugins.licensemanagement.menu",
checkAccess = {
LicensePluginService.instance.hasAccess()
})
// Register the menu entry as sub menu entry of the misc menu:
pluginWicketRegistrationService.registerMenuItem(
MenuItemDefId.MISC, menuItemDef,
LicenseListPage::class.java
)

// Define the access management:
registerRight(LicenseManagementRight())

// All the i18n stuff:
addResourceBundle(RESOURCE_BUNDLE_NAME)
}

companion object {
const val ID: String = "licenseManagement"

const val RESOURCE_BUNDLE_NAME: String = "LicenseManagementI18nResources"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2025 Micromata GmbH, Germany (www.micromata.com)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition 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 General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.plugins.licensemanagement

import jakarta.annotation.PostConstruct
import mu.KotlinLogging
import org.projectforge.business.user.UserGroupCache
import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service

private val log = KotlinLogging.logger {}

@Service
class LicensePluginService {
/**
* List of groups that are allowed to manage and see licenses.
* ALL for all users, empty/NONE for no user, or csv list of group pk's or group names.
*/
@Value("\${projectforge.plugins.license.allowedGroups}")
var allowedGroups: String? = null

@Autowired
private lateinit var userGroupCache: UserGroupCache

private var allowedGroupIds = emptyList<Long>()

private var allUsersAllowed = false

@PostConstruct
fun postConstruct() {
instance = this
run {
allowedGroups?.split(",;")?.forEach { entry ->
val trimmed = entry.trim()
if (trimmed.isEmpty()) {
return@forEach
} else if (trimmed.equals("ALL", ignoreCase = true)) {
allowedGroupIds = emptyList()
allUsersAllowed = true
return@run // No need to continue. break
} else if (trimmed.equals("NONE", ignoreCase = true)) {
allowedGroupIds = emptyList()
return@run // No need to continue. break
}
val groupId = trimmed.toLongOrNull()
if (groupId != null) {
userGroupCache.getGroup(groupId)?.id.let { id ->
if (id != null) {
allowedGroupIds += id
} else {
log.warn("No group with id $groupId found for license management in projectforge.properties:projectforge.plugins.license.allowedGroups=$allowedGroups")
}
}
allowedGroupIds += groupId
} else {
userGroupCache.getGroupByName(trimmed)?.id.let { id ->
if (id != null) {
allowedGroupIds += id
} else {
log.warn("No group with name '$trimmed' found for license management in projectforge.properties:projectforge.plugins.license.allowedGroups=$allowedGroups")
}
}
}
}
}
if (allUsersAllowed) {
log.info { "Allowed groups for license management: ALL users." }
} else if (allowedGroupIds.isEmpty()) {
log.info { "No groups allowed for license management (not visible and usable for any user)." }
} else {
log.info {
"Allowed groups for license management: ${
allowedGroupIds.joinToString {
userGroupCache.getGroup(
it
)?.name ?: "???"
}
}"
}
}
}

fun hasAccess(): Boolean {
if (allUsersAllowed) {
return true
}
userGroupCache.getUserGroups(ThreadLocalUserContext.requiredLoggedInUser)?.forEach { groupId ->
if (allowedGroupIds.contains(groupId)) {
return true
}
}
return false
}

companion object {
@JvmStatic
lateinit var instance: LicensePluginService
private set
}
}
2 changes: 1 addition & 1 deletion projectforge-application/src/main/resources/i18nKeys.json
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@
{"i18nKey":"contact.type.own","bundleName":"I18nResources","translation":"own","translationDE":"eigene","usedInClasses":[],"usedInFiles":[]},
{"i18nKey":"contact.type.postal","bundleName":"I18nResources","translation":"postal","translationDE":"postalisch","usedInClasses":[],"usedInFiles":[]},
{"i18nKey":"contact.type.private","bundleName":"I18nResources","translation":"private","translationDE":"privat","usedInClasses":[],"usedInFiles":[]},
{"i18nKey":"contactPerson","bundleName":"I18nResources","translation":"Contact person","translationDE":"Ansprechpartner:in","usedInClasses":["org.projectforge.business.fibu.AuftragDO","org.projectforge.business.fibu.AuftragDao","org.projectforge.business.fibu.OrderExport","org.projectforge.rest.fibu.AuftragPagesRest","org.projectforge.rest.orga.VisitorbookEntryPageRest","org.projectforge.web.fibu.AuftragEditForm"],"usedInFiles":["./projectforge-business/src/main/resources/mail/orderChangeNotification.html"]},
{"i18nKey":"contactPerson","bundleName":"I18nResources","translation":"Contact person","translationDE":"Ansprechpartner:in","usedInClasses":["org.projectforge.business.fibu.AuftragDO","org.projectforge.business.fibu.AuftragDao","org.projectforge.business.fibu.OrderExport","org.projectforge.rest.fibu.AuftragPagesRest","org.projectforge.web.fibu.AuftragEditForm"],"usedInFiles":["./projectforge-business/src/main/resources/mail/orderChangeNotification.html"]},
{"i18nKey":"contextMenu.cancel","bundleName":"I18nResources","translation":"Cancel","translationDE":"Abbrechen","usedInClasses":["org.projectforge.web.wicket.AbstractUnsecureBasePage"],"usedInFiles":[]},
{"i18nKey":"contextMenu.newTab","bundleName":"I18nResources","translation":"Open in new tab","translationDE":"Link in neuem Tab öffnen","usedInClasses":["org.projectforge.web.wicket.AbstractUnsecureBasePage"],"usedInFiles":[]},
{"i18nKey":"copy","bundleName":"I18nResources","translation":"Copy","translationDE":"Kopieren","usedInClasses":["org.projectforge.web.teamcal.integration.TeamCalCalendarPanel"],"usedInFiles":["./projectforge-wicket/src/main/java/org/projectforge/web/gantt/GanttChartEditTreeTablePanel.html"]},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ public boolean hasRight(final PFUserDO origUser, final IUserRightId rightId, fin
}
return false;
}
final UserRightDO rightDO = user.getRight(rightId);
final UserRightDO rightDO = userGroupCache.getUserRight(user.getId(), rightId);
final UserRight right = userRights.getRight(rightId);
final Collection<GroupDO> userGroups = userGroupCache.getUserGroupDOs(user);
for (final UserRightValue value : values) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ open class VisitorbookDO : DefaultBaseDO() {
@PropertyInfo(i18nKey = "orga.visitorbook.contactPersons")
@IndexedEmbedded(includeDepth = 2, includePaths = ["user.firstname", "user.lastname"])
@IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW)
@get:ManyToMany(targetEntity = EmployeeDO::class, fetch = FetchType.LAZY)
@get:ManyToMany(fetch = FetchType.LAZY)
@get:JoinTable(
name = "T_ORGA_VISITORBOOK_EMPLOYEE",
joinColumns = [JoinColumn(name = "VISITORBOOK_ID")],
inverseJoinColumns = [JoinColumn(name = "EMPLOYEE_ID")],
joinColumns = [JoinColumn(name = "VISITORBOOK_ID", referencedColumnName = "PK")],
inverseJoinColumns = [JoinColumn(name = "EMPLOYEE_ID", referencedColumnName = "PK")],
indexes = [jakarta.persistence.Index(
name = "idx_fk_t_orga_visitorbook_employee_id",
columnList = "visitorbook_id"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ open class UserDao : BaseDao<PFUserDO>(PFUserDO::class.java) {
return list
}

fun getUserRights(userId: Long?): List<UserRightDO>? {
fun getUserRights(userId: Long?): Collection<UserRightDO>? {
return userGroupCache.getUserRights(userId)
}

Expand Down
Loading

0 comments on commit 7bfe892

Please sign in to comment.