package com.almworks.jira.structure.api;
import com.almworks.jira.structure.util.JiraFunc;
import com.atlassian.crowd.embedded.api.Group;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.project.Project;
import com.atlassian.jira.project.ProjectManager;
import com.atlassian.jira.security.roles.ProjectRoleManager;
import org.jetbrains.annotations.Nullable;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import java.io.Serializable;
import java.text.ParseException;
import java.util.SortedSet;
import static org.apache.commons.lang.StringUtils.removeStart;
/**
* <p><code>PermissionSubject</code> is an abstraction used to specify which users a particular permission
* is applicable to. All possible sub-classes of <code>PermissionSubject</code> are listed in this class, and other implementation are not
* supported because Structure needs to serialize and deserialize permission subjects.</p>
*
* @see PermissionSubject.JiraUser
* @see PermissionSubject.JiraGroup
* @see PermissionSubject.ProjectRole
* @see PermissionSubject.Anyone
* @see <a href="http://wiki.almworks.com/display/structure/Structure+Permissions">Structure Permissions (Structure Documentation)</a>
* @author Igor Sereda
*/
@XmlRootElement
@XmlSeeAlso({PermissionSubject.Anyone.class, PermissionSubject.JiraUser.class, PermissionSubject.JiraGroup.class,
PermissionSubject.ProjectRole.class})
public abstract class PermissionSubject implements Cloneable {
/**
* Used to check whether a user matches this subject.
*
* @param user user to check, null means anonymous
* @return true if the user matches
*/
public abstract boolean matches(@Nullable User user);
/**
* @return a string representation of this permission subject
* @see #fromEncodedString
*/
public abstract String toEncodedString();
/**
* Creates a <code>PermissionSubject</code> based on the string representation. Null parameter yields null result.
*
* @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
*/
@Nullable
public static PermissionSubject fromEncodedString(@Nullable String s) 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)) 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();
}
/**
* 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(User user) {
return true;
}
public String toEncodedString() {
return "anyone";
}
public boolean equals(Object obj) {
return obj instanceof Anyone;
}
public int hashCode() {
return Anyone.class.hashCode();
}
}
/**
* Represents a specific user in JIRA, matching only that user.
*/
@XmlRootElement(name = "user")
@XmlType(name = "user")
public static class JiraUser extends PermissionSubject {
private String myUserName;
public JiraUser() {
}
public JiraUser(String userName) {
myUserName = userName;
}
@XmlAttribute(name = "name")
public String getUserName() {
return myUserName;
}
public void setUserName(String userName) {
myUserName = userName;
}
public boolean matches(User user) {
return myUserName == null ? user == null : user != null && myUserName.equals(user.getName());
}
public String toEncodedString() {
return "user:" + myUserName;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JiraUser jiraUser = (JiraUser) o;
if (myUserName != null ? !myUserName.equals(jiraUser.myUserName) : jiraUser.myUserName != null) return false;
return true;
}
public int hashCode() {
return myUserName != null ? myUserName.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 {
private String myGroupName;
public JiraGroup() {
}
public JiraGroup(String groupName) {
myGroupName = groupName;
}
@XmlAttribute(name = "name")
public String getGroupName() {
return myGroupName;
}
public void setGroupName(String groupName) {
myGroupName = groupName;
}
public boolean matches(User user) {
if(myGroupName == null || user == null) {
return false;
}
SortedSet<Group> groups = ComponentAccessor.getUserUtil().getGroupsForUser(user.getName());
return JiraFunc.GROUP_NAME.find(groups, myGroupName) != null;
}
public String toEncodedString() {
return "group:" + myGroupName;
}
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
public long getProjectId() {
return myProjectId;
}
public void setProjectId(long projectId) {
myProjectId = projectId;
}
@XmlAttribute
public long getRoleId() {
return myRoleId;
}
public void setRoleId(long roleId) {
myRoleId = roleId;
}
public boolean matches(User user) {
if (myProjectId == 0 || myRoleId == 0) {
return false;
}
ProjectManager pm = ComponentAccessor.getProjectManager();
Project project = pm.getProjectObj(myProjectId);
if (project == null) {
return false;
}
ProjectRoleManager rm = ComponentAccessor.getComponentOfType(ProjectRoleManager.class);
com.atlassian.jira.security.roles.ProjectRole role = rm.getProjectRole(myRoleId);
if (role == null) {
return false;
}
return rm.isUserInProjectRole(user, role, project);
}
public String toEncodedString() {
return "role:" + myProjectId + ":" + myRoleId;
}
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;
}
}
}