com.almworks.jira.structure.api.permissions.PermissionSubject Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of structure-api Show documentation
Show all versions of structure-api Show documentation
Public API for the Structure Plugin for JIRA
The newest version!
package com.almworks.jira.structure.api.permissions;
import com.almworks.jira.structure.api.settings.StructureConfiguration;
import com.almworks.jira.structure.api.util.*;
import com.atlassian.crowd.embedded.api.Group;
import com.atlassian.jira.project.Project;
import com.atlassian.jira.project.ProjectManager;
import com.atlassian.jira.security.roles.ProjectRoleManager;
import com.atlassian.jira.user.ApplicationUser;
import com.fasterxml.jackson.annotation.*;
import org.jetbrains.annotations.Nullable;
import javax.xml.bind.annotation.*;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Collections;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.removeStart;
/**
* PermissionSubject
is an abstraction used to specify which users a particular permission
* is applicable to. All possible sub-classes of PermissionSubject
are listed in this class, and other implementation are not
* supported because Structure needs to serialize and deserialize permission subjects.
*
* @see PermissionSubject.JiraUser
* @see PermissionSubject.JiraGroup
* @see PermissionSubject.ProjectRole
* @see PermissionSubject.Anyone
* @see Structure Permissions (Structure Documentation)
* @author Igor Sereda
*/
@XmlRootElement
@XmlSeeAlso({PermissionSubject.Anyone.class, PermissionSubject.JiraUser.class, PermissionSubject.JiraGroup.class,
PermissionSubject.ProjectRole.class})
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = PermissionSubject.Anyone.class, name = "anyone"),
@JsonSubTypes.Type(value = PermissionSubject.JiraUser.class, name = "user"),
@JsonSubTypes.Type(value = PermissionSubject.JiraGroup.class, name = "group"),
@JsonSubTypes.Type(value = PermissionSubject.ProjectRole.class, name = "project.role")
})
public abstract class PermissionSubject implements Cloneable {
public abstract boolean matches(@Nullable ApplicationUser user);
/**
* @return a string representation of this permission subject
* @see #fromEncodedString
*/
public abstract String toEncodedString();
/**
* Creates a PermissionSubject
based on the string representation. Null parameter yields null result.
* Use this method only if user keys were used to encode user permissions.
*
* @param s string representation of the PermissionSubject
* @return decoded PermissionSubject, or null if s was null or an empty string.
* @throws ParseException if s was not null neither empty, but the code failed to decipher the string
* @see #fromEncodedString(String, boolean)
*/
@Nullable
public static PermissionSubject fromEncodedString(@Nullable String s) throws ParseException {
return fromEncodedString(s, false);
}
/**
* Creates a PermissionSubject
based on the string representation. Null parameter yields null result.
*
* @param s string representation of the PermissionSubject
* @param usersAsUserNames true if user names were used to encode user permissions (Structure version was less than 2.3),
* false if user keys were used instead.
* @return decoded PermissionSubject, or null if s was null or an empty string.
* @throws ParseException if s was not null neither empty, but the code failed to decipher the string
*/
@Nullable
public static PermissionSubject fromEncodedString(@Nullable String s, boolean usersAsUserNames) throws ParseException {
if (s == null || s.length() == 0) return null;
String p = removeStart(s, "anyone");
if (!p.equals(s)) return new Anyone();
p = removeStart(s, "user:");
if (!p.equals(s)) {
if (usersAsUserNames) p = StructureUtil.migrateUserNameToUserKey(p);
return new JiraUser(p);
}
p = removeStart(s, "group:");
if (!p.equals(s)) return new JiraGroup(p);
p = removeStart(s, "role:");
if (!p.equals(s)) {
int k = p.indexOf(':');
if (k < 0) return null;
try {
long project = Long.parseLong(p.substring(0, k));
long role = Long.parseLong(p.substring(k + 1));
return new ProjectRole(project, role);
} catch (NumberFormatException e) {
throw new ParseException(s, 0);
}
}
throw new ParseException(s, 0);
}
/**
* @return a clone of this subject
*/
@SuppressWarnings( {"CloneDoesntDeclareCloneNotSupportedException"})
public PermissionSubject clone() {
// must be overridden - but defining here to get rid of CloneNotSupportedException
try {
return (PermissionSubject) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
public String toString() {
return toEncodedString();
}
/**
* Calls an appropriate visitor method, passing this PermissionSubject and the carry parameter.
* @return the result of the call
* @since 7.2.0 (Structure 2.0)
*/
@Nullable
public abstract T visit(Visitor visitor, @Nullable T carry);
/**
* Represents "anyone", a subject that would match all users, even anonymous.
*/
@XmlRootElement
public static class Anyone extends PermissionSubject implements Serializable {
public Anyone() {
}
public boolean matches(ApplicationUser user) {
return true;
}
public String toEncodedString() {
return "anyone";
}
public boolean equals(Object obj) {
return obj instanceof Anyone;
}
@Override
public T visit(Visitor visitor, T carry) {
return visitor.onAnyone(this, carry);
}
public int hashCode() {
return Anyone.class.hashCode();
}
}
@Nullable
public static PermissionSubject clone(@Nullable PermissionSubject owner) {
return owner == null ? null : owner.clone();
}
/**
* Represents a specific user in JIRA, matching only that user.
*
* A user is identified by the user key, introduced in JIRA 6. This is different from structure-api 7.x, which
* used user name as the ID. As a result, serialized permission subjects from JIRA 5.x may not correctly
* deserialize with this new version of the API - migration is needed. See {@link StructureUtil#migrateUserNameToUserKey(String)}.
*/
@XmlRootElement(name = "user")
@XmlType(name = "user")
public static class JiraUser extends PermissionSubject {
@Nullable
private String myUserKey;
public JiraUser() {
}
public JiraUser(@Nullable String userKey) {
myUserKey = userKey;
}
public JiraUser(@Nullable ApplicationUser user) {
myUserKey = JiraUsers.getKeyFor(user);
}
@Nullable
@XmlTransient
@JsonIgnore
public String getUserName() {
return StructureUtil.getUserNameByKey(myUserKey);
}
@Nullable
@XmlAttribute(name = "name")
@JsonProperty("name")
public String getUserKey() {
return myUserKey;
}
public void setUserName(@Nullable String userName) {
ApplicationUser user = StructureUtil.getApplicationUserByName(userName);
myUserKey = StructureUtil.getUserKey(user);
}
public void setUserKey(@Nullable String userKey) {
myUserKey = userKey;
}
public boolean matches(ApplicationUser user) {
return myUserKey == null ? user == null : myUserKey.equals(JiraUsers.getKeyFor(user));
}
public String toEncodedString() {
return "user:" + myUserKey;
}
@Override
public T visit(Visitor visitor, T carry) {
return visitor.onUser(this, carry);
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JiraUser jiraUser = (JiraUser) o;
if (myUserKey != null ? !myUserKey.equals(jiraUser.myUserKey) : jiraUser.myUserKey != null) return false;
return true;
}
public int hashCode() {
return myUserKey != null ? myUserKey.hashCode() : 0;
}
}
/**
* Represents a specific group in JIRA, matching only the users that belong to that group.
*/
@XmlRootElement(name = "group")
@XmlType(name = "group")
public static class JiraGroup extends PermissionSubject {
@Nullable
private String myGroupName;
public JiraGroup() {
}
public JiraGroup(@Nullable String groupName) {
myGroupName = groupName;
}
public JiraGroup(@Nullable Group group) {
myGroupName = group == null ? null : group.getName();
}
@Nullable
@XmlAttribute(name = "name")
@JsonProperty("name")
public String getGroupName() {
return myGroupName;
}
public void setGroupName(@Nullable String groupName) {
myGroupName = groupName;
}
public boolean matches(ApplicationUser user) {
if (myGroupName == null || user == null) {
return false;
}
return JiraComponents.getGroupManager().isUserInGroup(user, myGroupName);
}
public String toEncodedString() {
return "group:" + myGroupName;
}
@Override
public T visit(Visitor visitor, T carry) {
return visitor.onGroup(this, carry);
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JiraGroup jiraGroup = (JiraGroup) o;
if (myGroupName != null ? !myGroupName.equals(jiraGroup.myGroupName) : jiraGroup.myGroupName != null)
return false;
return true;
}
public int hashCode() {
return myGroupName != null ? myGroupName.hashCode() : 0;
}
}
/**
* Represents a specific project role in a specific project, matching the users that belong to that project role
* in that project.
*/
@XmlRootElement(name = "role")
@XmlType(name = "role")
public static class ProjectRole extends PermissionSubject {
private long myProjectId;
private long myRoleId;
public ProjectRole() {
}
public ProjectRole(long projectId, long roleId) {
myProjectId = projectId;
myRoleId = roleId;
}
@XmlAttribute
@JsonProperty("project")
public long getProjectId() {
return myProjectId;
}
public void setProjectId(long projectId) {
myProjectId = projectId;
}
@XmlAttribute
@JsonProperty("role")
public long getRoleId() {
return myRoleId;
}
public void setRoleId(long roleId) {
myRoleId = roleId;
}
public boolean matches(ApplicationUser user) {
if (myRoleId == 0) {
return false;
}
ProjectRoleManager rm = JiraComponents.getComponentOfType(ProjectRoleManager.class);
com.atlassian.jira.security.roles.ProjectRole role = rm.getProjectRole(myRoleId);
if (role == null) {
return false;
}
List projects;
if (myProjectId == 0) {
StructureConfiguration configuration = JiraComponents.getOSGiComponentInstanceOfType(StructureConfiguration.class);
if (configuration == null) {
return false;
}
projects = configuration.getCurrentlyEnabledProjects();
} else {
ProjectManager pm = JiraComponents.getProjectManager();
Project project = pm.getProjectObj(myProjectId);
projects = project == null ? Collections.emptyList() : Collections.singletonList(project);
}
for (Project project : projects) {
if (rm.isUserInProjectRole(user, role, project)) {
return true;
}
}
return false;
}
public String toEncodedString() {
return "role:" + myProjectId + ":" + myRoleId;
}
@Override
public T visit(Visitor visitor, T carry) {
return visitor.onProjectRole(this, carry);
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProjectRole that = (ProjectRole) o;
if (myProjectId != that.myProjectId) return false;
if (myRoleId != that.myRoleId) return false;
return true;
}
public int hashCode() {
int result = (int) (myProjectId ^ (myProjectId >>> 32));
result = 31 * result + (int) (myRoleId ^ (myRoleId >>> 32));
return result;
}
}
/**
* Used to visit specific subtypes of {@link PermissionSubject}.
*
* @param arbitrary type used to pass a value in and out of visitor (carry). Use {@link Visitor.NoCarry} if you
* don't need it.
*/
public interface Visitor {
T onAnyone(Anyone anyone, T carry);
T onUser(JiraUser user, T carry);
T onGroup(JiraGroup group, T carry);
T onProjectRole(ProjectRole projectRole, T carry);
public static abstract class NoCarry implements Visitor {
public abstract void onAnyone(Anyone anyone);
public abstract void onUser(JiraUser user);
public abstract void onGroup(JiraGroup group);
public abstract void onProjectRole(ProjectRole projectRole);
@Override
public Void onAnyone(Anyone anyone, Void carry) {
onAnyone(anyone);
return carry;
}
@Override
public Void onUser(JiraUser user, Void carry) {
onUser(user);
return carry;
}
@Override
public Void onGroup(JiraGroup group, Void carry) {
onGroup(group);
return carry;
}
@Override
public Void onProjectRole(ProjectRole projectRole, Void carry) {
onProjectRole(projectRole);
return carry;
}
}
}
}