package hudson.plugins.collabnet.auth;
import com.collabnet.ce.webservices.CTFList;
import com.collabnet.ce.webservices.CTFProject;
import com.collabnet.ce.webservices.CTFRole;
import com.collabnet.ce.webservices.CTFUser;
import com.collabnet.ce.webservices.CollabNetApp;
import hudson.Extension;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import hudson.plugins.collabnet.util.ComboBoxUpdater;
import hudson.plugins.collabnet.util.CommonUtil;
import hudson.security.Permission;
import hudson.util.ComboBoxModel;
import hudson.util.FormValidation;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
/**
* Job property to associate a Hudson job with a CollabNet Project for
* Authorization purposes (used with CollabNet Authorization).
*/
public class CNAuthProjectProperty extends JobProperty<Job<?, ?>> {
public static Permission CONFIGURE_PROPERTY = Item.CONFIGURE;
private transient boolean mIsNotLoadedFromDisk = false;
private transient String project = null;
private String projectId = null;
private boolean createRoles = false;
private boolean grantDefaultRoles = false;
private static Logger log = Logger.getLogger("CNAuthProjectProperty");
private static Collection<String> defaultAdminRoles =
Collections.emptyList();
private static Collection<String> defaultUserRoles =
Collections.emptyList();
/**
* Constructor
* @param project name of project to tie the auth to
* @param createRoles true to create special hudson roles
* @param grantDefaultRoles true to grant default roles to project members
*/
@DataBoundConstructor
public CNAuthProjectProperty(String project, boolean createRoles, String storedProjectId,
boolean grantDefaultRoles) {
this.project = project;
this.createRoles = createRoles;
this.grantDefaultRoles = grantDefaultRoles;
if (this.createRoles || this.grantDefaultRoles) {
this.loadRoles();
}
mIsNotLoadedFromDisk = true;
// if the user who configured the job didn't have the authority to change the project binding,
// revert it back to what it was before.
loadProjectIdIfNecessary();
if (!CommonUtil.isEmpty(getProject()) && CommonUtil.isEmpty(getProjectId())) {
// means we can't find the specified project name - prevent overriding
setProjectId(storedProjectId);
}
}
/**
* Determine the project id.
*/
private void loadProjectIdIfNecessary() {
if (CommonUtil.isEmpty(projectId) && !CommonUtil.isEmpty(project)) {
CollabNetApp conn = CNConnection.getInstance();
if (conn == null) {
return;
}
try {
CTFProject p = conn.getProjectByTitle(project);
projectId = p!=null ? p.getId() : null;
} catch (RemoteException e) {
projectId = null;
log.log(WARNING,"Failed to load project ID of "+project,e);
}
if (!mIsNotLoadedFromDisk) {
if (this.owner != null) {
try {
mIsNotLoadedFromDisk = true;
this.owner.save(); // should save the conf file for the job
} catch (IOException e) {
log.info("Failed to modify config file for migration of project name to project id");
}
}
}
}
}
/**
* @return the name of the CollabNet project.
*/
public String getProject() {
loadProjectIdIfNecessary();
if (!CommonUtil.isEmpty(projectId)) {
// always use the name from project id if project id exists - this allows us to address scenario where
// while the app is running, the project name was changed on the server
CollabNetApp conn = CNConnection.getInstance();
if (conn != null) {
try {
CTFProject p = conn.getProjectById(projectId);
if (p!=null)
return p.getTitle();
} catch (RemoteException e) {
// fall back to the stored project name
}
}
}
return project;
}
/**
* @return the id of the TeamForge project.
*/
public String getProjectId() {
loadProjectIdIfNecessary();
return projectId;
}
/**
* Set the project id and reprocure the corresponding project name
* @param projectId project id
*/
public void setProjectId(String projectId) {
this.projectId = projectId;
}
/**
* @return true if creating the roles on the CollabNet server should be
* attempted.
*/
public boolean getCreateRoles() {
return this.createRoles;
}
/**
* @return true if the default roles should be added.
*/
public boolean getGrantDefaultRoles() {
return this.grantDefaultRoles;
}
/**
* @return the default user roles. Lazily initialized.
*/
public Collection<String> getDefaultUserRoles() {
if (CNAuthProjectProperty.defaultUserRoles.isEmpty()) {
CNAuthProjectProperty.defaultUserRoles = new ArrayList<String>();
CNAuthProjectProperty.defaultUserRoles.add("Hudson Read");
}
return CNAuthProjectProperty.defaultUserRoles;
}
/**
* @return the default admin roles. Lazily initialized.
*/
public Collection<String> getDefaultAdminRoles() {
if (CNAuthProjectProperty.defaultAdminRoles.isEmpty()) {
CNAuthProjectProperty.defaultAdminRoles =
CNProjectACL.CollabNetRoles.getNames();
}
return CNAuthProjectProperty.defaultAdminRoles;
}
/**
* Load the roles into CSFE, if they are not already present.
* Requires the logged in user to be a project admin in the
* CollabNet project.
*
*/
private void loadRoles() {
String projectIdStr = getProjectId();
if (!CommonUtil.isEmpty(projectIdStr)) {
try {
CollabNetApp conn = CNConnection.getInstance();
if (conn == null) {
log.warning("Cannot loadRoles, incorrect authentication type.");
return;
}
CTFProject p = conn.getProjectById(getProjectId());
if (this.getCreateRoles()) {
CTFList<CTFRole> existing = p.getRoles();
for (CollabNetRole role: CNProjectACL.CollabNetRoles.getAllRoles()) {
if (existing.byTitle(role.getName())==null)
p.createRole(role.getName(), role.getDescription());
}
}
if (this.getGrantDefaultRoles()) {
// load up some default roles
// this should be an option later
grantRoles(p, this.getDefaultUserRoles(), p.getMembers());
grantRoles(p, this.getDefaultAdminRoles(), p.getAdmins());
}
} catch (RemoteException e) {
log.log(WARNING, "Cannot loadRoles, incorrect authentication type.", e);
}
}
}
private void grantRoles(CTFProject p, Collection<String> roleNames, List<CTFUser> members) throws RemoteException {
CTFList<CTFRole> roles = p.getRoles();
for (String name : roleNames) {
CTFRole r = roles.byTitle(name);
if (r==null) continue; // this indicates an abstraction leakage
CTFList<CTFUser> existing = r.getMembers();
for (CTFUser m : members) {
if (!existing.contains(m))
try {
r.grant(m);
} catch (RemoteException re) {
log.severe("grantRoles: failed with RemoteException: " +
re.getMessage());
}
}
}
}
/**
* Descriptor class.
*/
@Extension
public static class DescriptorImpl extends JobPropertyDescriptor {
/**
* @return string to display.
*/
@Override
public String getDisplayName() {
return "Associated CollabNet Project";
}
/**
* @param jobType
* @return true when the CNAuthorizationStrategy is in effect.
*/
@Override
public boolean isApplicable(Class<? extends Job> jobType) {
// only applicable when using CNAuthorizationStrategy
return Hudson.getInstance().getAuthorizationStrategy()
instanceof CNAuthorizationStrategy;
}
/**
* Form validation for the project field.
*/
public FormValidation doCheckProject(@QueryParameter String value) throws RemoteException {
String project = value;
if (CommonUtil.isEmpty(project)) {
return FormValidation.warning("If left empty, all users will be able to configure and access this " +
"build");
}
CNAuthentication auth = CNAuthentication.get();
if (auth == null) {
return FormValidation.warning("Cannot check project name, improper" +
" authentication type.");
}
CTFProject p = auth.getCredentials().getProjectByTitle(project);
boolean superUser = auth.isSuperUser();
boolean hudsonAdmin = Hudson.getInstance().getACL()
.hasPermission(Hudson.ADMINISTER);
if (p==null) {
if (superUser) {
return FormValidation.error("This project does not exist.");
} else {
return FormValidation.error("The current user does not have access " +
"to this project. This setting change will not be saved.");
}
}
FormValidation ok = FormValidation.ok("Currently selected project: " + p.getId() + ":" + project);
if (superUser) {
// all other errors should not be valid for a
// superuser, since superusers are Hudson Admins
// (so all-powerful in the Hudson realm) and also
// all-powerful in the CollabNet server.
return ok;
}
if (!auth.isProjectAdmin(p)) {
return FormValidation.warning("The current user is not a project admin in " +
"the project, so he/she cannot create or " +
"grant roles.");
}
if (hudsonAdmin) {
// no more errors apply to the Hudson Admin, since
// admins will never be locked out of this page.
return ok;
}
// check that the user will have configure permissions
// on this page
CNProjectACL acl = new CNProjectACL(p.getId());
if (!acl.hasPermission(CNAuthProjectProperty
.CONFIGURE_PROPERTY)) {
CollabNetRole roleNeeded =
CNProjectACL.CollabNetRoles
.getGrantingRole(CNAuthProjectProperty
.CONFIGURE_PROPERTY);
return FormValidation.warning("The current user does not have the '" +
roleNeeded.getName() + "' role in the " +
"project, which is required to configure " +
"this Hudson job. If this project is chosen," +
" the current user will not have the power " +
"to change the project later, unless he/she " +
"is given this role.");
}
return ok;
}
/**
* Get a list of projects to choose from.
*
* @return an array of project names.
*/
public ComboBoxModel doFillProjectItems() throws RemoteException {
CollabNetApp conn = CNConnection.getInstance();
if (conn == null) {
return new ComboBoxModel();
}
return ComboBoxUpdater.toModel(conn.getProjects());
}
/**
* @return the CollabNet server url.
*/
public String getCollabNetUrl() {
CollabNetApp conn = CNConnection.getInstance();
return conn.getServerUrl();
}
}
}