├── .gitignore ├── Jenkinsfile ├── README.md ├── pom.xml └── src ├── main ├── java │ └── hudson │ │ └── plugins │ │ └── redmine │ │ ├── MetricsAction.java │ │ ├── MetricsException.java │ │ ├── MetricsGraph.java │ │ ├── MetricsProjectAction.java │ │ ├── MetricsResult.java │ │ ├── RedmineAuthenticationException.java │ │ ├── RedmineLinkAction.java │ │ ├── RedmineLinkAnnotator.java │ │ ├── RedmineMetricsCalculator.java │ │ ├── RedmineMetricsPublisher.java │ │ ├── RedmineProjectProperty.java │ │ ├── RedmineRepositoryBrowser.java │ │ ├── RedmineSecurityRealm.java │ │ ├── RedmineUserData.java │ │ ├── RedmineUserDetails.java │ │ ├── RedmineWebsiteConfig.java │ │ ├── VersionUtil.java │ │ ├── dao │ │ ├── AbstractAuthDao.java │ │ ├── MySQLAuthDao.java │ │ └── PostgreSQLAuthDao.java │ │ └── util │ │ ├── CipherUtil.java │ │ └── Constants.java ├── resources │ ├── hudson │ │ └── plugins │ │ │ └── redmine │ │ │ ├── Messages.properties │ │ │ ├── Messages_ja.properties │ │ │ ├── MetricsAction │ │ │ └── index.jelly │ │ │ ├── MetricsProjectAction │ │ │ ├── floatingBox.jelly │ │ │ └── index.jelly │ │ │ ├── RedmineMetricsPublisher │ │ │ ├── config.jelly │ │ │ ├── config.properties │ │ │ ├── config_ja.properties │ │ │ ├── help-apiKey.html │ │ │ ├── help-apiKey_ja.html │ │ │ ├── help-ignoreTicketStatus.html │ │ │ ├── help-ignoreTicketTracker.html │ │ │ ├── help-targetVersion.html │ │ │ ├── help-targetVersion_ja.html │ │ │ └── help.html │ │ │ ├── RedmineProjectProperty │ │ │ ├── config.jelly │ │ │ ├── config.properties │ │ │ ├── global.jelly │ │ │ ├── help-projectName.html │ │ │ └── help.html │ │ │ ├── RedmineRepositoryBrowser │ │ │ ├── config.jelly │ │ │ └── config.properties │ │ │ ├── RedmineSecurityRealm │ │ │ ├── config.jelly │ │ │ ├── config.properties │ │ │ ├── config_ja.properties │ │ │ ├── help-databaseName.html │ │ │ ├── help-databaseName_ja.html │ │ │ ├── help-dbPassword.html │ │ │ ├── help-dbPassword_ja.html │ │ │ ├── help-dbServer.html │ │ │ ├── help-dbServer_ja.html │ │ │ ├── help-dbUserName.html │ │ │ ├── help-dbUserName_ja.html │ │ │ ├── help-loginTable.html │ │ │ ├── help-loginTable_ja.html │ │ │ ├── help-passField.html │ │ │ ├── help-passField_ja.html │ │ │ ├── help-port.html │ │ │ ├── help-port_ja.html │ │ │ ├── help-saltField.html │ │ │ ├── help-saltField_ja.html │ │ │ ├── help-userField.html │ │ │ ├── help-userField_ja.html │ │ │ ├── help-version.html │ │ │ └── help-version_ja.html │ │ │ └── RedmineWebsiteConfig │ │ │ ├── config.jelly │ │ │ └── config.properties │ └── index.jelly └── webapp │ ├── help-auth-overview.html │ ├── help-auth-overview_ja.html │ ├── help-global.html │ ├── help-repo.html │ ├── help-version.html │ ├── redmine-logo.png │ └── ruby-logo-R.png └── test └── java └── hudson └── plugins └── redmine ├── RedmineLinkAnnotatorTest.java ├── RedmineMetricsCalculatorTest.java └── VersionUtilTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | #java specific 9 | *.class 10 | .classpath 11 | .project 12 | .settings 13 | target 14 | work 15 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | /* `buildPlugin` step provided by: https://github.com/jenkins-infra/pipeline-library */ 4 | buildPlugin() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Redmine plugin 2 | ================== 3 | 4 | What's this? 5 | ------------ 6 | 7 | https://wiki.jenkins-ci.org/display/JENKINS/Redmine+Plugin 8 | 9 | Does this plugin require custom Redmine configuration? 10 | ------------ 11 | 12 | This plugin uses Redmine REST API - to enable it, login into your Redmine, 13 | navigate to Administration -> Settings -> Authentication, check "Enable REST web service" and Save. 14 | 15 | To use "Aggregate Redmine ticket metrics" you should provide API key assigned to existing Redmine user. 16 | Login as desired user, visit http://redmine/my/account, copy key from "API access key" and paste it into job on Jenkins. 17 | 18 | Contribute 19 | ------------ 20 | 21 | Fork and send a pull request (or create an issue on github) 22 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.jenkins-ci.plugins 7 | plugin 8 | 1.466 9 | 10 | 11 | redmine 12 | hpi 13 | 0.22-SNAPSHOT 14 | 15 | Jenkins Redmine plugin 16 | This plugin integrates Redmine into Jenkins 17 | http://wiki.jenkins-ci.org/display/JENKINS/Redmine+Plugin 18 | 19 | 20 | 21 | gaooh 22 | Akiko Asami 23 | 24 | 25 | ljader 26 | Lukasz Jader 27 | 28 | 29 | 30 | 31 | UTF-8 32 | 33 | 34 | 35 | 36 | 37 | maven-release-plugin 38 | 39 | deploy 40 | 41 | 42 | 43 | org.jenkins-ci.tools 44 | maven-hpi-plugin 45 | true 46 | 47 | 0.14 48 | 49 | 50 | 51 | 52 | 53 | 54 | scm:git:git://github.com/jenkinsci/redmine-plugin.git 55 | scm:git:git@github.com:jenkinsci/redmine-plugin.git 56 | https://github.com/jenkinsci/redmine-plugin 57 | HEAD 58 | 59 | 60 | 61 | 62 | org.jvnet.hudson.plugins 63 | subversion 64 | 2.0.1 65 | 66 | 67 | com.taskadapter 68 | redmine-java-api 69 | 1.15 70 | 71 | 72 | com.googlecode.jmockit 73 | jmockit 74 | 0.999.17 75 | test 76 | 77 | 78 | mysql 79 | mysql-connector-java 80 | 5.1.21 81 | 82 | 83 | postgresql 84 | postgresql 85 | 9.1-901-1.jdbc4 86 | 87 | 88 | 89 | 90 | 91 | false 92 | maven.jenkins-ci.org 93 | https://repo.jenkins-ci.org/releases/ 94 | 95 | 96 | maven.jenkins-ci.org 97 | https://repo.jenkins-ci.org/snapshots/ 98 | 99 | 100 | 101 | 102 | 103 | repo.jenkins-ci.org 104 | https://repo.jenkins-ci.org/public/ 105 | 106 | 107 | 108 | 109 | 110 | repo.jenkins-ci.org 111 | https://repo.jenkins-ci.org/public/ 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/MetricsAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import hudson.model.Action; 4 | import hudson.model.Result; 5 | import hudson.model.AbstractBuild; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class MetricsAction implements Action { 11 | 12 | private AbstractBuild build; 13 | private List metricsList; 14 | 15 | public MetricsAction(AbstractBuild build, 16 | List metricsList) { 17 | this.build = build; 18 | this.metricsList = metricsList; 19 | } 20 | 21 | public String getIconFileName() { 22 | return "notepad.png"; 23 | } 24 | 25 | public String getDisplayName() { 26 | return Messages.ticket_metrics_detail(); 27 | } 28 | 29 | public String getUrlName() { 30 | return "ticketMetrics"; 31 | } 32 | 33 | public AbstractBuild getBuild() { 34 | return build; 35 | } 36 | 37 | public List getMetricsList() { 38 | return metricsList; 39 | } 40 | 41 | public List> getPreviousMetricsLists() { 42 | @SuppressWarnings("unchecked") 43 | List> builds = (List>) build 44 | .getPreviousBuildsOverThreshold(1000, Result.SUCCESS); 45 | List> results = new ArrayList>(); 46 | for (AbstractBuild abstractBuild : builds) { 47 | MetricsAction action = abstractBuild.getAction(MetricsAction.class); 48 | if (action == null) { 49 | continue; 50 | } 51 | results.add(action.getMetricsList()); 52 | } 53 | return results; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/MetricsException.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import com.taskadapter.redmineapi.RedmineException; 4 | 5 | public class MetricsException extends Exception { 6 | 7 | private static final long serialVersionUID = 7335586079552372270L; 8 | 9 | public MetricsException(RedmineException e) { 10 | super(e); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/MetricsGraph.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import hudson.model.AbstractProject; 4 | import hudson.model.Run; 5 | import hudson.util.Graph; 6 | import hudson.util.RunList; 7 | import hudson.util.ShiftedCategoryAxis; 8 | 9 | import java.awt.Color; 10 | import java.util.ArrayList; 11 | import java.util.Calendar; 12 | import java.util.Collections; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | import org.jfree.chart.ChartFactory; 18 | import org.jfree.chart.JFreeChart; 19 | import org.jfree.chart.axis.CategoryAxis; 20 | import org.jfree.chart.axis.CategoryLabelPositions; 21 | import org.jfree.chart.axis.NumberAxis; 22 | import org.jfree.chart.plot.CategoryPlot; 23 | import org.jfree.chart.plot.PlotOrientation; 24 | import org.jfree.data.category.DefaultCategoryDataset; 25 | import org.jfree.ui.RectangleInsets; 26 | 27 | public class MetricsGraph extends Graph { 28 | 29 | private AbstractProject project; 30 | 31 | private DefaultCategoryDataset createDataset() { 32 | List actions = new ArrayList(); 33 | RunList builds = project.getBuilds(); 34 | for (Run run : builds) { 35 | MetricsAction action = run.getAction(MetricsAction.class); 36 | if (action == null) { 37 | continue; 38 | } 39 | actions.add(action); 40 | } 41 | 42 | Collections.reverse(actions); 43 | Set statusSet = new HashSet(); 44 | 45 | DefaultCategoryDataset ds = new DefaultCategoryDataset(); 46 | for (MetricsAction action : actions) { 47 | String buildNum = "#" + action.getBuild().getNumber(); 48 | boolean addedValue = false; 49 | List metricsList = action.getMetricsList(); 50 | for (MetricsResult result : metricsList) { 51 | statusSet.add(result.getStatus()); 52 | ds.addValue(result.getCount(), result.getStatus(), buildNum); 53 | addedValue = true; 54 | } 55 | if (!addedValue) { 56 | for (String status : statusSet) { 57 | ds.addValue(0, status, buildNum); 58 | } 59 | } 60 | } 61 | return ds; 62 | } 63 | 64 | public MetricsGraph(AbstractProject project) { 65 | super(Calendar.getInstance(), 640, 480); 66 | this.project = project; 67 | } 68 | 69 | @Override 70 | protected JFreeChart createGraph() { 71 | DefaultCategoryDataset dataset = createDataset(); 72 | 73 | JFreeChart chart = ChartFactory.createStackedAreaChart("Ticket", 74 | "BuildNum", "Count", dataset, PlotOrientation.VERTICAL, true, 75 | true, false); 76 | chart.setBackgroundPaint(Color.white); 77 | 78 | CategoryPlot plot = (CategoryPlot) chart.getPlot(); 79 | plot.setBackgroundPaint(Color.WHITE); 80 | plot.setOutlinePaint(null); 81 | plot.setRangeGridlinesVisible(true); 82 | plot.setRangeGridlinePaint(Color.black); 83 | 84 | CategoryAxis domainAxis = new ShiftedCategoryAxis(null); 85 | plot.setDomainAxis(domainAxis); 86 | domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90); 87 | domainAxis.setLowerMargin(0.0); 88 | domainAxis.setUpperMargin(0.0); 89 | domainAxis.setCategoryMargin(0.0); 90 | 91 | NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); 92 | rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); 93 | rangeAxis.setAutoRange(true); 94 | 95 | plot.setInsets(new RectangleInsets(0, 0, 0, 5.0)); 96 | return chart; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/MetricsProjectAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import hudson.model.Action; 4 | import hudson.model.AbstractProject; 5 | import hudson.util.Graph; 6 | 7 | public class MetricsProjectAction implements Action { 8 | 9 | private AbstractProject project; 10 | 11 | public MetricsProjectAction(AbstractProject project) { 12 | this.project = project; 13 | } 14 | 15 | public String getIconFileName() { 16 | return "graph.gif"; 17 | } 18 | 19 | public String getDisplayName() { 20 | return Messages.ticket_metrics(); 21 | } 22 | 23 | public String getUrlName() { 24 | return "metricsProject"; 25 | } 26 | 27 | public Graph getGraph() { 28 | return new MetricsGraph(project); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/MetricsResult.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | public class MetricsResult { 4 | private String status; 5 | private int count; 6 | 7 | public MetricsResult(String status, int count) { 8 | this.status = status; 9 | this.count = count; 10 | } 11 | 12 | public String getStatus() { 13 | return status; 14 | } 15 | 16 | public int getCount() { 17 | return count; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineAuthenticationException.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import org.acegisecurity.AuthenticationException; 4 | 5 | /** 6 | * 7 | * @author Yasuyuki Saito 8 | * 9 | */ 10 | public class RedmineAuthenticationException extends AuthenticationException { 11 | 12 | /** */ 13 | private static final long serialVersionUID = 1L; 14 | 15 | /** 16 | * 17 | * @param msg 18 | * @param t 19 | */ 20 | public RedmineAuthenticationException(String msg, Throwable t) { 21 | super(msg, t); 22 | } 23 | 24 | /** 25 | * 26 | * @param msg 27 | */ 28 | public RedmineAuthenticationException(String msg) { 29 | super(msg); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineLinkAction.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import hudson.model.Action; 4 | 5 | /** 6 | * @author gaooh 7 | * @date 2008/10/26 8 | */ 9 | public class RedmineLinkAction implements Action { 10 | private final RedmineProjectProperty prop; 11 | 12 | public RedmineLinkAction(RedmineProjectProperty prop) { 13 | this.prop = prop; 14 | } 15 | 16 | public String getIconFileName() { 17 | RedmineWebsiteConfig redmineConfig = prop.getRedmineWebsite(); 18 | if (redmineConfig == null) { 19 | return null; 20 | } else { 21 | return "/plugin/redmine/redmine-logo.png"; // redmine logo instead ruby 22 | } 23 | } 24 | 25 | public String getDisplayName() { 26 | return "Redmine - " + prop.projectName; 27 | } 28 | 29 | public String getUrlName() { 30 | RedmineWebsiteConfig redmineConfig = prop.getRedmineWebsite(); 31 | if (redmineConfig == null) { 32 | return null; 33 | } else { 34 | return redmineConfig.baseUrl + "projects/" + prop.projectName; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineLinkAnnotator.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import hudson.Extension; 4 | import hudson.MarkupText; 5 | import hudson.MarkupText.SubText; 6 | import hudson.model.AbstractBuild; 7 | import hudson.scm.ChangeLogAnnotator; 8 | import hudson.scm.ChangeLogSet.Entry; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | import org.apache.commons.lang.StringUtils; 13 | 14 | /** 15 | * Annotates RedmineLink 16 | * notation in changelog messages. 17 | * 18 | * @author gaooh 19 | * @date 2008/10/13 20 | */ 21 | @Extension 22 | public class RedmineLinkAnnotator extends ChangeLogAnnotator { 23 | 24 | private static boolean isVersionBefore120; 25 | 26 | @Override 27 | public void annotate(AbstractBuild build, Entry change, MarkupText text) { 28 | RedmineProjectProperty rpp = build.getProject().getProperty(RedmineProjectProperty.class); 29 | if(rpp == null || rpp.getRedmineWebsite() == null) { // not configured 30 | return; 31 | } 32 | 33 | String url = rpp.getRedmineWebsite().baseUrl; 34 | isVersionBefore120 = VersionUtil.isVersionBefore120(rpp.getRedmineWebsite().versionNumber); 35 | LinkMarkup[] markups = MARKUPS; 36 | if(isVersionBefore120) { 37 | markups = MARKUPS_OLD; 38 | } 39 | 40 | for (LinkMarkup markup : markups) { 41 | markup.process(text, url); 42 | } 43 | } 44 | 45 | static final class LinkMarkup { 46 | private final Pattern pattern; 47 | private final String href; 48 | 49 | LinkMarkup(String pattern, String href) { 50 | pattern = NUM_PATTERN.matcher(pattern).replaceAll("([\\\\d|,| |&|#]+)"); // \\\\d becomes \\d when in the expanded text. 51 | pattern = ANYWORD_PATTERN.matcher(pattern).replaceAll("((?:\\\\w|[._-])+)"); 52 | this.pattern = Pattern.compile(pattern); 53 | this.href = href; 54 | } 55 | 56 | void process(MarkupText text, String url) { 57 | 58 | for(SubText st : text.findTokens(pattern)) { 59 | String[] message = st.getText().split(" ", 2); 60 | 61 | if (message.length > 1) { 62 | String[] nums = message[1].split(",|&| "); 63 | String splitValue = ","; 64 | if(message[1].indexOf("&") != -1) { 65 | splitValue = "&"; 66 | } else if(message[1].indexOf("#") != -1) { 67 | splitValue = "#"; 68 | } else if(message[1].indexOf(" ") != -1) { 69 | splitValue = " "; 70 | } 71 | 72 | if(nums.length > 1) { 73 | int startpos = 0; 74 | int endpos = message[0].length() + nums[0].length() + 1; 75 | nums[0] = nums[0].replace("#", ""); 76 | st.addMarkup(startpos, endpos, getIssuesUrl(url, nums[0]), ""); 77 | 78 | startpos = endpos + splitValue.length(); 79 | endpos = startpos; 80 | 81 | for(int i = 1 ; i < nums.length ; i++) { 82 | endpos += nums[i].length() ; 83 | if(i != 1) { 84 | endpos += splitValue.length(); 85 | } 86 | if(endpos >= st.getText().length()) { 87 | endpos = st.getText().length(); 88 | } 89 | if(StringUtils.isNotBlank(nums[i])) { 90 | nums[i] = nums[i].replace("#", ""); 91 | st.addMarkup(startpos, endpos, getIssuesUrl(url, nums[i]), ""); 92 | } 93 | startpos = endpos + splitValue.length(); 94 | 95 | } 96 | } else { 97 | st.surroundWith("",""); 98 | } 99 | } else { 100 | st.surroundWith("",""); 101 | } 102 | } 103 | } 104 | 105 | private String getIssuesUrl(String url, String num) { 106 | if(isVersionBefore120) { 107 | return ""; 108 | } 109 | return ""; 110 | } 111 | 112 | private static final Pattern NUM_PATTERN = Pattern.compile("NUM"); 113 | private static final Pattern ANYWORD_PATTERN = Pattern.compile("ANYWORD"); 114 | 115 | } 116 | 117 | static final LinkMarkup[] MARKUPS = new LinkMarkup[] { 118 | new LinkMarkup( 119 | "(?:#|refs |references |IssueID |fixes |closes )#?NUM", 120 | "issues/$1"), 121 | new LinkMarkup( 122 | "((?:[A-Z][a-z]+){2,})|wiki:ANYWORD", 123 | "wiki/$1$2"), 124 | }; 125 | static final LinkMarkup[] MARKUPS_OLD = new LinkMarkup[] { 126 | new LinkMarkup( 127 | "(?:#|refs |references |IssueID |fixes |closes )#?NUM", 128 | "issues/show/$1"), 129 | new LinkMarkup( 130 | "((?:[A-Z][a-z]+){2,})|wiki:ANYWORD", 131 | "wiki/$1$2"), 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineMetricsCalculator.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | 9 | import org.apache.commons.lang.ArrayUtils; 10 | import com.taskadapter.redmineapi.RedmineException; 11 | import com.taskadapter.redmineapi.RedmineManager; 12 | import com.taskadapter.redmineapi.bean.Issue; 13 | import com.taskadapter.redmineapi.bean.Project; 14 | import com.taskadapter.redmineapi.bean.Version; 15 | 16 | public class RedmineMetricsCalculator { 17 | 18 | private String url; 19 | private String apiKey; 20 | /** actually a project identifier - project names in Redmine can have spaces */ 21 | private String projectName; 22 | private String versions; 23 | private String ignoreTicketTracker; 24 | private String ignoreTicketStatus; 25 | 26 | public RedmineMetricsCalculator(String url, String apiKey, 27 | String projectName, String versions, String ignoreTicketTracker, 28 | String ignoreTicketStatus) { 29 | this.url = url; 30 | this.apiKey = apiKey; 31 | this.projectName = projectName; 32 | this.versions = versions; 33 | this.ignoreTicketTracker = ignoreTicketTracker; 34 | this.ignoreTicketStatus = ignoreTicketStatus; 35 | } 36 | 37 | public List calc() throws MetricsException { 38 | List result = new ArrayList(); 39 | try { 40 | RedmineManager manager = new RedmineManager(url, apiKey); 41 | 42 | Project proj = getProject(manager); 43 | 44 | List versionsList = getVersionsString(manager, proj); 45 | 46 | Map tmpCalcMap = new HashMap(); 47 | for (String v : versionsList) { 48 | Map params = new HashMap(); 49 | params.put("project_id", proj.getId().toString()); 50 | params.put("fixed_version_id", v); 51 | params.put("status_id", "*"); 52 | 53 | for (Issue issue : manager.getIssues(params)) { 54 | if (!isTargetTracker(issue)) { 55 | continue; 56 | } 57 | if (!isTargetStatus(issue)) { 58 | continue; 59 | } 60 | 61 | String status = issue.getStatusName(); 62 | if (!tmpCalcMap.containsKey(status)) { 63 | tmpCalcMap.put(status, 0); 64 | } 65 | Integer count = tmpCalcMap.get(status); 66 | tmpCalcMap.put(status, count + 1); 67 | } 68 | } 69 | for (Entry e : tmpCalcMap.entrySet()) { 70 | result.add(new MetricsResult(e.getKey(), e.getValue())); 71 | } 72 | } catch (RedmineException e) { 73 | throw new MetricsException(e); 74 | } 75 | return result; 76 | } 77 | 78 | private boolean isTargetTracker(Issue issue) { 79 | if (ignoreTicketTracker == null || ignoreTicketTracker.isEmpty()) { 80 | return true; 81 | } 82 | return !ArrayUtils.contains(ignoreTicketTracker.split(","), issue 83 | .getTracker().getName()); 84 | } 85 | 86 | private boolean isTargetStatus(Issue issue) { 87 | if (ignoreTicketStatus == null || ignoreTicketStatus.isEmpty()) { 88 | return true; 89 | } 90 | return !ArrayUtils.contains(ignoreTicketStatus.split(","), 91 | issue.getStatusName()); 92 | } 93 | 94 | private Project getProject(RedmineManager manager) throws RedmineException { 95 | List projects = manager.getProjects(); 96 | for (Project proj : projects) { 97 | if (projectName.equalsIgnoreCase(proj.getIdentifier())) { 98 | return proj; 99 | } 100 | } 101 | for (Project proj : projects) { 102 | if (projectName.equals(proj.getName())) { 103 | return proj; 104 | } 105 | } 106 | throw new RedmineException("No such project. projectName=" + projectName); 107 | } 108 | 109 | private List getVersionsString(RedmineManager manager, Project proj) 110 | throws RedmineException { 111 | List allVersions = manager.getVersions(proj.getId()); 112 | if (versions.isEmpty()) { 113 | return allVersionsWithNull(allVersions); 114 | } 115 | List vs = new ArrayList(); 116 | String[] versionStrings = versions.split(","); 117 | for (String string : versionStrings) { 118 | for (Version v : allVersions) { 119 | if (string.trim().equals(v.getName())) { 120 | vs.add(String.valueOf(v.getId())); 121 | } 122 | } 123 | } 124 | return vs; 125 | } 126 | 127 | private List allVersionsWithNull(List versionList) { 128 | List vs = new ArrayList(); 129 | vs.add("!*"); 130 | for (Version v : versionList) { 131 | vs.add(String.valueOf(v.getId())); 132 | } 133 | return vs; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineMetricsPublisher.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import hudson.Extension; 4 | import hudson.Launcher; 5 | import hudson.Util; 6 | import hudson.model.Action; 7 | import hudson.model.BuildListener; 8 | import hudson.model.AbstractBuild; 9 | import hudson.model.AbstractProject; 10 | import hudson.tasks.BuildStepDescriptor; 11 | import hudson.tasks.BuildStepMonitor; 12 | import hudson.tasks.Publisher; 13 | import hudson.util.FormValidation; 14 | import hudson.util.Secret; 15 | 16 | import java.io.IOException; 17 | import java.io.PrintStream; 18 | import java.net.MalformedURLException; 19 | import java.net.URL; 20 | import java.util.List; 21 | 22 | import org.kohsuke.stapler.DataBoundConstructor; 23 | import org.kohsuke.stapler.QueryParameter; 24 | 25 | public class RedmineMetricsPublisher extends Publisher { 26 | 27 | private Secret apiKey; 28 | private String targetVersion; 29 | private String ignoreTicketTracker; 30 | private String ignoreTicketStatus; 31 | 32 | @SuppressWarnings("deprecation") 33 | @DataBoundConstructor 34 | public RedmineMetricsPublisher(String apiKey, String targetVersion, String ignoreTicketTracker, 35 | String ignoreTicketStatus) { 36 | this.apiKey = Secret.fromString(Util.fixEmptyAndTrim(apiKey)); 37 | this.targetVersion = targetVersion; 38 | this.ignoreTicketTracker = ignoreTicketTracker; 39 | this.ignoreTicketStatus = ignoreTicketStatus; 40 | } 41 | 42 | @Override 43 | public boolean perform(AbstractBuild build, Launcher launcher, 44 | BuildListener listener) throws InterruptedException, IOException { 45 | RedmineProjectProperty rpp = build.getProject().getProperty(RedmineProjectProperty.class); 46 | if(rpp == null || rpp.getRedmineWebsite() == null) { // not configured 47 | return false; 48 | } 49 | 50 | PrintStream logger = listener.getLogger(); 51 | 52 | RedmineMetricsCalculator calculator = new RedmineMetricsCalculator(rpp.getRedmineWebsite().baseUrl, 53 | apiKey.getPlainText(), rpp.projectName, targetVersion, ignoreTicketTracker, 54 | ignoreTicketStatus); 55 | try { 56 | List metricsList = calculator.calc(); 57 | MetricsAction metricsAction = new MetricsAction(build, metricsList); 58 | build.addAction(metricsAction); 59 | } catch (MetricsException e) { 60 | logger.println(e); 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | public BuildStepMonitor getRequiredMonitorService() { 68 | return BuildStepMonitor.NONE; 69 | } 70 | 71 | public Secret getApiKey() { 72 | return apiKey; 73 | } 74 | 75 | public String getTargetVersion() { 76 | return targetVersion; 77 | } 78 | 79 | public String getIgnoreTicketTracker() { 80 | return ignoreTicketTracker; 81 | } 82 | 83 | public String getIgnoreTicketStatus() { 84 | return ignoreTicketStatus; 85 | } 86 | 87 | @Override 88 | public Action getProjectAction(AbstractProject project) { 89 | return new MetricsProjectAction(project); 90 | } 91 | 92 | @Extension 93 | public static class DescriptorImpl extends BuildStepDescriptor { 94 | 95 | @SuppressWarnings("rawtypes") 96 | @Override 97 | public boolean isApplicable(Class jobType) { 98 | return true; 99 | } 100 | 101 | public FormValidation doCheckUrl(@QueryParameter String url) { 102 | if (url.length() == 0) { 103 | return FormValidation.error(Messages 104 | .error_require_redmine_url()); 105 | } 106 | try { 107 | new URL(url); 108 | } catch (MalformedURLException e) { 109 | return FormValidation.error(Messages 110 | .error_invalid_redmine_url()); 111 | } 112 | return FormValidation.ok(); 113 | } 114 | 115 | public FormValidation doCheckApiKey(@QueryParameter String value) { 116 | if (value.length() == 0) { 117 | return FormValidation.error(Messages.error_require_api_key()); 118 | } 119 | return FormValidation.ok(); 120 | } 121 | 122 | public FormValidation doCheckProjectName( 123 | @QueryParameter String projectName) { 124 | if (projectName.length() == 0) { 125 | return FormValidation.error(Messages 126 | .error_require_project_name()); 127 | } 128 | return FormValidation.ok(); 129 | } 130 | 131 | @Override 132 | public String getDisplayName() { 133 | return Messages.aggregate_redmine_ticket_metrics(); 134 | } 135 | 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineProjectProperty.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import java.util.Collection; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | import hudson.Extension; 9 | import hudson.model.AbstractProject; 10 | import hudson.model.Action; 11 | import hudson.model.Job; 12 | import hudson.model.JobProperty; 13 | import hudson.model.JobPropertyDescriptor; 14 | import hudson.util.CopyOnWriteList; 15 | import hudson.util.FormValidation; 16 | 17 | import net.sf.json.JSONObject; 18 | 19 | import org.apache.commons.collections.CollectionUtils; 20 | import org.apache.commons.collections.Predicate; 21 | import org.apache.commons.lang.StringUtils; 22 | import org.kohsuke.stapler.DataBoundConstructor; 23 | import org.kohsuke.stapler.QueryParameter; 24 | import org.kohsuke.stapler.StaplerRequest; 25 | 26 | import com.google.common.collect.Sets; 27 | 28 | /** 29 | * Property for {@link AbstractProject} that stores the associated Redmine website URL. 30 | * 31 | * @author gaooh 32 | * @date 2008/10/13 33 | */ 34 | public class RedmineProjectProperty extends JobProperty> { 35 | private final String redmineWebsiteName; 36 | /** actually a project identifier - project names in Redmine can have spaces */ 37 | public final String projectName; 38 | 39 | @DataBoundConstructor 40 | public RedmineProjectProperty(String redmineWebsiteName, String projectName) { 41 | this.redmineWebsiteName = redmineWebsiteName; 42 | this.projectName = projectName; 43 | } 44 | 45 | @Override 46 | public Collection getJobActions(AbstractProject job) { 47 | return Collections.singletonList(new RedmineLinkAction(this)); 48 | } 49 | 50 | public RedmineWebsiteConfig getRedmineWebsite() { 51 | if (redmineWebsiteName == null) { 52 | return null; 53 | } 54 | 55 | RedmineWebsiteConfig foundRedmine = null; 56 | for (RedmineWebsiteConfig redmineConfig : DESCRIPTOR.getRedmineWebsites()) { 57 | if (redmineConfig.name.equals(redmineWebsiteName)){ 58 | foundRedmine = redmineConfig; 59 | break; 60 | } 61 | } 62 | return foundRedmine; 63 | } 64 | 65 | @Override 66 | public JobPropertyDescriptor getDescriptor() { 67 | return DESCRIPTOR; 68 | } 69 | 70 | @Extension 71 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 72 | 73 | public static final class DescriptorImpl extends JobPropertyDescriptor { 74 | private final CopyOnWriteList redmineWebsites = new CopyOnWriteList(); 75 | 76 | public DescriptorImpl() { 77 | super(RedmineProjectProperty.class); 78 | load(); 79 | } 80 | 81 | @Override 82 | public boolean isApplicable(Class jobType) { 83 | return AbstractProject.class.isAssignableFrom(jobType); 84 | } 85 | 86 | public String getDisplayName() { 87 | return "Associated Redmine website"; 88 | } 89 | 90 | @Override 91 | public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { 92 | List redmineSites = req.bindJSONToList(RedmineWebsiteConfig.class, 93 | formData.get("redmineWebsites")); 94 | CollectionUtils.filter(redmineSites, new Predicate() { 95 | public boolean evaluate(Object object) { 96 | return StringUtils.isNotBlank(((RedmineWebsiteConfig) object).name) 97 | && StringUtils.isNotBlank(((RedmineWebsiteConfig) object).baseUrl); 98 | } 99 | }); 100 | CollectionUtils.filter(redmineSites, new Predicate() { 101 | Set redmineNames = Sets.newHashSet(); 102 | 103 | public boolean evaluate(Object object) { 104 | String examinedName = ((RedmineWebsiteConfig) object).name; 105 | if (redmineNames.contains(examinedName)){ 106 | return false; 107 | } 108 | redmineNames.add(examinedName); 109 | return true; 110 | } 111 | }); 112 | 113 | this.redmineWebsites.replaceBy(redmineSites); 114 | 115 | save(); 116 | return super.configure(req, formData); 117 | } 118 | 119 | @Override 120 | public JobProperty newInstance(StaplerRequest req, JSONObject formData) throws FormException { 121 | if (formData.containsKey("redmine")){ 122 | JSONObject redmineJson = formData.getJSONObject("redmine"); 123 | if (!StringUtils.isBlank(redmineJson.optString("redmineWebsiteName")) 124 | && !StringUtils.isBlank(redmineJson.optString("projectName"))) { 125 | return req.bindJSON(RedmineProjectProperty.class, redmineJson); 126 | } 127 | } 128 | 129 | return null; 130 | } 131 | 132 | public List getRedmineWebsites() { 133 | return this.redmineWebsites.getView(); 134 | } 135 | 136 | public FormValidation doCheckProjectName(@QueryParameter String projectName) { 137 | if (projectName == null || projectName.trim().length() < 1) { 138 | return FormValidation.error("Project name can't be empty!"); 139 | } 140 | 141 | //We dont validate existence yet 142 | return FormValidation.ok(); 143 | } 144 | 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineRepositoryBrowser.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import java.io.IOException; 4 | import java.net.MalformedURLException; 5 | import java.net.URL; 6 | 7 | import hudson.scm.SubversionChangeLogSet; 8 | import org.kohsuke.stapler.DataBoundConstructor; 9 | 10 | import hudson.Extension; 11 | import hudson.model.AbstractProject; 12 | import hudson.model.Descriptor; 13 | import hudson.scm.EditType; 14 | import hudson.scm.RepositoryBrowser; 15 | import hudson.scm.SubversionRepositoryBrowser; 16 | import hudson.scm.SubversionChangeLogSet.LogEntry; 17 | import hudson.scm.SubversionChangeLogSet.Path; 18 | 19 | /** 20 | * produces redmine links. 21 | * 22 | * @author gaooh 23 | */ 24 | public class RedmineRepositoryBrowser extends SubversionRepositoryBrowser { 25 | private final String repositoryId; 26 | 27 | @DataBoundConstructor 28 | public RedmineRepositoryBrowser(String repositoryId) { 29 | this.repositoryId = repositoryId; 30 | } 31 | 32 | /** 33 | * @deprecated use {@link #RedmineRepositoryBrowser(String)} 34 | */ 35 | @Deprecated 36 | public RedmineRepositoryBrowser() { 37 | this(null); 38 | } 39 | 40 | public String getRepositoryId() { 41 | return repositoryId; 42 | } 43 | 44 | @Override 45 | public URL getDiffLink(Path path) throws IOException { 46 | if(path.getEditType()!= EditType.EDIT) { 47 | return null; 48 | } 49 | String filePath = getFilePath(path.getLogEntry(), path.getValue()); 50 | int revision = path.getLogEntry().getRevision(); 51 | 52 | if (isVersionBefore090(path.getLogEntry())) { 53 | URL baseUrl = getRedmineURL(path.getLogEntry()); 54 | String projectName = getProject(path.getLogEntry()); 55 | 56 | return new URL(baseUrl, "repositories/diff/" + projectName + filePath + "?rev=" + revision); 57 | } else { 58 | URL baseUrl = getRedmineProjectURL(path.getLogEntry()); 59 | String id = getRepositoryId(path.getLogEntry()); 60 | 61 | return new URL(baseUrl, "repository" + id + "/diff" + filePath + "?rev=" + revision); 62 | } 63 | } 64 | 65 | @Override 66 | public URL getFileLink(Path path) throws IOException { 67 | String filePath = getFilePath(path.getLogEntry(), path.getValue()); 68 | 69 | if (isVersionBefore090(path.getLogEntry())) { 70 | URL baseUrl = getRedmineURL(path.getLogEntry()); 71 | String projectName = getProject(path.getLogEntry()); 72 | 73 | return baseUrl == null ? null : new URL(baseUrl, "repositories/entry/" + projectName + filePath); 74 | } else { 75 | URL baseUrl = getRedmineProjectURL(path.getLogEntry()); 76 | String id = getRepositoryId(path.getLogEntry()); 77 | int revision = path.getLogEntry().getRevision(); 78 | 79 | return baseUrl == null ? null : new URL(baseUrl, "repository"+ id + "/revisions/"+ revision+"/entry" +filePath); 80 | } 81 | } 82 | 83 | @Override 84 | public URL getChangeSetLink(LogEntry changeSet) throws IOException { 85 | if (isVersionBefore090(changeSet)) { 86 | URL baseUrl = getRedmineURL(changeSet); 87 | String projectName = getProject(changeSet); 88 | return baseUrl == null ? null : new URL(baseUrl, "repositories/revision/" + projectName + "/" + changeSet.getRevision()); 89 | } else { 90 | URL baseUrl = getRedmineProjectURL(changeSet); 91 | String id = getRepositoryId(changeSet); 92 | return baseUrl == null ? null : new URL(baseUrl, "repository" + id + "/revisions/" + changeSet.getRevision()); 93 | } 94 | } 95 | 96 | @Override 97 | public Descriptor> getDescriptor() { 98 | return DESCRIPTOR; 99 | } 100 | 101 | private URL getRedmineURL(LogEntry logEntry) throws MalformedURLException { 102 | AbstractProject p = (AbstractProject)logEntry.getParent().build.getProject(); 103 | RedmineProjectProperty rpp = p.getProperty(RedmineProjectProperty.class); 104 | if(rpp == null) { 105 | return null; 106 | } else { 107 | return new URL(rpp.getRedmineWebsite().baseUrl); 108 | } 109 | } 110 | 111 | private String getProject(LogEntry logEntry) { 112 | AbstractProject p = (AbstractProject)logEntry.getParent().build.getProject(); 113 | RedmineProjectProperty rpp = p.getProperty(RedmineProjectProperty.class); 114 | if(rpp == null) { 115 | return null; 116 | } else { 117 | return rpp.projectName; 118 | } 119 | } 120 | 121 | /** 122 | * The raw Redmine URL is documented with the example http://myhost/redmine. This 123 | * function returns the _project_'s base URL which concatenates the redmine base url 124 | * with the "/projects//" (if a project name is supplied) 125 | * @param logEntry 126 | * @return 127 | * @throws MalformedURLException 128 | */ 129 | private URL getRedmineProjectURL(LogEntry logEntry) throws MalformedURLException { 130 | AbstractProject p = (AbstractProject)logEntry.getParent().build.getProject(); 131 | RedmineProjectProperty rpp = p.getProperty(RedmineProjectProperty.class); 132 | String url; 133 | if(rpp == null || rpp.getRedmineWebsite() == null) { 134 | url = ""; 135 | } else { 136 | // NOTE: we force the website string to have a trailing slash in the constructor 137 | url = rpp.getRedmineWebsite().baseUrl; 138 | if (rpp.projectName != null) { 139 | url += "projects/" + rpp.projectName + "/"; 140 | } 141 | } 142 | return new URL(url); 143 | } 144 | 145 | private String getRepositoryId(LogEntry logEntry) { 146 | if (this.repositoryId == null || this.repositoryId.trim().length() == 0){ 147 | return ""; 148 | } else { 149 | return "/" + this.repositoryId.trim(); 150 | } 151 | } 152 | 153 | private String getFilePath(LogEntry logEntry, String fileFullPath) { 154 | AbstractProject p = (AbstractProject)logEntry.getParent().build.getProject(); 155 | RedmineProjectProperty rpp = p.getProperty(RedmineProjectProperty.class); 156 | 157 | String filePath = ""; 158 | if(VersionUtil.isVersionBefore081(rpp.getRedmineWebsite().versionNumber)) { 159 | String[] filePaths = fileFullPath.split("/"); 160 | filePath = "/"; 161 | if(filePaths.length > 2) { 162 | for(int i = 2 ; i < filePaths.length; i++) { 163 | filePath = filePath + filePaths[i]; 164 | if(i != filePaths.length - 1) { 165 | filePath = filePath + "/"; 166 | } 167 | } 168 | } 169 | } else { 170 | filePath = fileFullPath; 171 | } 172 | return filePath; 173 | 174 | } 175 | 176 | private boolean isVersionBefore090(LogEntry logEntry) { 177 | SubversionChangeLogSet parent = logEntry.getParent(); 178 | if (parent == null || parent.build == null) { 179 | return false; 180 | } 181 | AbstractProject p = parent.build.getProject(); 182 | RedmineProjectProperty rpp = p.getProperty(RedmineProjectProperty.class); 183 | return rpp != null && VersionUtil.isVersionBefore090(rpp.getRedmineWebsite().versionNumber); 184 | } 185 | 186 | 187 | @Extension 188 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 189 | 190 | public static final class DescriptorImpl extends Descriptor> { 191 | public DescriptorImpl() { 192 | super(RedmineRepositoryBrowser.class); 193 | } 194 | 195 | public String getDisplayName() { 196 | return "Redmine"; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineSecurityRealm.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import hudson.Extension; 4 | import hudson.Util; 5 | import hudson.model.Descriptor; 6 | import hudson.plugins.redmine.dao.*; 7 | import hudson.plugins.redmine.util.CipherUtil; 8 | import hudson.plugins.redmine.util.Constants; 9 | import hudson.security.AbstractPasswordBasedSecurityRealm; 10 | import hudson.security.GroupDetails; 11 | import hudson.security.SecurityRealm; 12 | import hudson.util.Secret; 13 | 14 | import java.util.HashSet; 15 | import java.util.Set; 16 | import java.util.logging.Logger; 17 | 18 | import org.acegisecurity.AuthenticationException; 19 | import org.acegisecurity.GrantedAuthority; 20 | import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; 21 | import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider; 22 | import org.acegisecurity.userdetails.UserDetails; 23 | import org.acegisecurity.userdetails.UsernameNotFoundException; 24 | import org.apache.commons.lang.StringUtils; 25 | import org.kohsuke.stapler.DataBoundConstructor; 26 | import org.springframework.dao.DataAccessException; 27 | 28 | /** 29 | * @author Yasuyuki Saito 30 | */ 31 | public class RedmineSecurityRealm extends AbstractPasswordBasedSecurityRealm { 32 | 33 | /** Logger */ 34 | private static final Logger LOGGER = Logger.getLogger(RedmineSecurityRealm.class.getName()); 35 | 36 | /** Redmine DBMS */ 37 | private final String dbms; 38 | 39 | /** DB Server */ 40 | private final String dbServer; 41 | 42 | /** Database Name */ 43 | private final String databaseName; 44 | 45 | /** Database Port */ 46 | private final String port; 47 | 48 | /** Database UserName */ 49 | private final String dbUserName; 50 | 51 | /** Database Password */ 52 | private final Secret dbPassword; 53 | 54 | /** Redmine Version */ 55 | private final String version; 56 | 57 | /** Redmine Login Table */ 58 | private final String loginTable; 59 | 60 | /** Redmine User Field */ 61 | private final String userField; 62 | 63 | /** Redmine Password Field */ 64 | private final String passField; 65 | 66 | /** Redmine Salt Field */ 67 | private final String saltField; 68 | 69 | /** 70 | * Constructor 71 | * @param dbms Redmine DBMS 72 | * @param dbServer DB Server 73 | * @param databaseName Database Name 74 | * @param port Database Port 75 | * @param dbUserName Database UserName 76 | * @param dbPassword Database Password 77 | * @param version Redmine Version 78 | * @param loginTable Redmine Login Table 79 | * @param userField Redmine User Field 80 | * @param passField Redmine Password Field 81 | * @param saltField Redmine Salt Field 82 | */ 83 | @DataBoundConstructor 84 | public RedmineSecurityRealm(String dbms, String dbServer, String databaseName, String port, String dbUserName, String dbPassword, 85 | String version, String loginTable, String userField, String passField, String saltField) { 86 | 87 | this.dbms = StringUtils.isBlank(dbms) ? Constants.DBMS_MYSQL : dbms; 88 | this.dbServer = StringUtils.isBlank(dbServer) ? Constants.DEFAULT_DB_SERVER : dbServer; 89 | this.databaseName = StringUtils.isBlank(databaseName) ? Constants.DEFAULT_DATABASE_NAME : databaseName; 90 | 91 | if (StringUtils.isBlank(port)) 92 | this.port = (Constants.DBMS_MYSQL.equals(this.dbms)) ? (Constants.DEFAULT_PORT_MYSQL) : (Constants.DBMS_POSTGRESQL); 93 | else 94 | this.port = port; 95 | 96 | this.dbUserName = dbUserName; 97 | this.dbPassword = Secret.fromString(Util.fixEmptyAndTrim(dbPassword)); 98 | this.version = StringUtils.isBlank(version) ? Constants.VERSION_1_2_0 : version; 99 | 100 | this.loginTable = StringUtils.isBlank(loginTable) ? Constants.DEFAULT_LOGIN_TABLE : loginTable; 101 | this.userField = StringUtils.isBlank(userField) ? Constants.DEFAULT_USER_FIELD : userField; 102 | this.passField = StringUtils.isBlank(passField) ? Constants.DEFAULT_PASSWORD_FIELD : passField; 103 | this.saltField = StringUtils.isBlank(saltField) ? Constants.DEFAULT_SALT_FIELD : saltField; 104 | } 105 | 106 | 107 | public static final class DescriptorImpl extends Descriptor { 108 | @Override 109 | public String getHelpFile() { 110 | return "/plugin/redmine/help-auth-overview.html"; 111 | } 112 | @Override 113 | public String getDisplayName() { 114 | return Messages.RedmineSecurityRealm_DisplayName(); 115 | } 116 | } 117 | 118 | 119 | @Extension 120 | public static DescriptorImpl install() { 121 | return new DescriptorImpl(); 122 | } 123 | 124 | /** 125 | * 126 | * @author Yasuyuki Saito 127 | */ 128 | class Authenticator extends AbstractUserDetailsAuthenticationProvider { 129 | @Override 130 | protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 131 | 132 | } 133 | 134 | @Override 135 | protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 136 | return RedmineSecurityRealm.this.authenticate(username, authentication.getCredentials().toString()); 137 | } 138 | } 139 | 140 | /** 141 | * 142 | * @param username Login UserName 143 | * @param password Login Password 144 | */ 145 | @Override 146 | protected UserDetails authenticate(String username, String password) throws AuthenticationException { 147 | AbstractAuthDao dao = null; 148 | 149 | try { 150 | dao = createAuthDao(this.dbms); 151 | 152 | LOGGER.info("Redmine DBMS : " + this.dbms); 153 | LOGGER.info("DB Server : " + this.dbServer); 154 | LOGGER.info("DB Port : " + this.port); 155 | LOGGER.info("Database Name : " + this.databaseName); 156 | 157 | dao.open(this.dbServer, this.port, this.databaseName, this.dbUserName, this.dbPassword.getPlainText()); 158 | 159 | if (!dao.isTable(this.loginTable)) 160 | throw new RedmineAuthenticationException("RedmineSecurity: Invalid Login Table"); 161 | 162 | if (!dao.isField(this.loginTable, this.userField)) 163 | throw new RedmineAuthenticationException("RedmineSecurity: Invalid User Field"); 164 | 165 | RedmineUserData userData = dao.getRedmineUserData(this.loginTable, this.userField, this.passField, Constants.VERSION_1_2_0.equals(this.version) ? this.saltField : null, username); 166 | 167 | if (userData == null) { 168 | LOGGER.warning("RedmineSecurity: Invalid Username"); 169 | throw new UsernameNotFoundException("RedmineSecurity: User not found"); 170 | } 171 | 172 | String encryptedPassword = ""; 173 | if (Constants.VERSION_1_2_0.equals(this.version)) { 174 | encryptedPassword = CipherUtil.encodeSHA1(userData.getSalt() + CipherUtil.encodeSHA1(password)); 175 | } else if (Constants.VERSION_1_1_3.equals(this.version)) { 176 | encryptedPassword = CipherUtil.encodeSHA1(password); 177 | } 178 | 179 | LOGGER.info("Redmine Version : " + this.version); 180 | LOGGER.info("User Name : " + username); 181 | LOGGER.info("Encrypted Password: " + encryptedPassword); 182 | 183 | if (!userData.getPassword().equals(encryptedPassword)) { 184 | LOGGER.warning("RedmineSecurity: Invalid Password"); 185 | throw new RedmineAuthenticationException("RedmineSecurity: Invalid Password"); 186 | } 187 | 188 | return getUserDetails(username, userData.getPassword()); 189 | } catch (AuthenticationException e) { 190 | throw e; 191 | } catch (Exception e) { 192 | throw new RedmineAuthenticationException("RedmineSecurity: System.Exception", e); 193 | } finally { 194 | if (dao != null) dao.close(); 195 | } 196 | } 197 | 198 | @Override 199 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { 200 | AbstractAuthDao dao = null; 201 | 202 | try { 203 | dao = createAuthDao(this.dbms); 204 | 205 | dao.open(this.dbServer, this.port, this.databaseName, this.dbUserName, this.dbPassword.getPlainText()); 206 | 207 | if (!dao.isTable(this.loginTable)) 208 | throw new RedmineAuthenticationException("RedmineSecurity: Invalid Login Table"); 209 | 210 | if (!dao.isField(this.loginTable, this.userField)) 211 | throw new RedmineAuthenticationException("RedmineSecurity: Invalid User Field"); 212 | 213 | RedmineUserData userData = dao.getRedmineUserData(this.loginTable, this.userField, this.passField, Constants.VERSION_1_2_0.equals(this.version) ? this.saltField : null, username); 214 | 215 | if (userData == null) { 216 | LOGGER.warning("RedmineSecurity: Invalid Username"); 217 | throw new UsernameNotFoundException("RedmineSecurity: User not found"); 218 | } 219 | 220 | return getUserDetails(username, userData.getPassword()); 221 | } catch (AuthenticationException e) { 222 | throw e; 223 | } catch (Exception e) { 224 | throw new RedmineAuthenticationException("RedmineSecurity: System.Exception", e); 225 | } finally { 226 | if (dao != null) dao.close(); 227 | } 228 | } 229 | 230 | /** 231 | * Create Auth Dao 232 | * @param dbms 233 | * @return 234 | */ 235 | private AbstractAuthDao createAuthDao(String dbms) { 236 | if (Constants.DBMS_MYSQL.equals(dbms)) 237 | return new MySQLAuthDao(); 238 | else if (Constants.DBMS_POSTGRESQL.equals(dbms)) 239 | return new PostgreSQLAuthDao(); 240 | else 241 | return null; 242 | } 243 | 244 | @Override 245 | public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException { 246 | throw new UsernameNotFoundException("RedmineSecurityRealm: Non-supported function"); 247 | } 248 | 249 | /** 250 | * 251 | * @param username 252 | * @param password 253 | * @return 254 | */ 255 | private UserDetails getUserDetails(String username, String password) { 256 | Set groups = new HashSet(); 257 | groups.add(SecurityRealm.AUTHENTICATED_AUTHORITY); 258 | return new RedmineUserDetails(username, password, true, true, true, true, groups.toArray(new GrantedAuthority[groups.size()])); 259 | } 260 | 261 | 262 | /** 263 | * 264 | * @return 265 | */ 266 | public String getDbms() { 267 | return dbms; 268 | } 269 | 270 | /** 271 | * 272 | * @return 273 | */ 274 | public String getDbServer() { 275 | return dbServer; 276 | } 277 | 278 | /** 279 | * 280 | * @return 281 | */ 282 | public String getDatabaseName() { 283 | return databaseName; 284 | } 285 | 286 | /** 287 | * 288 | * @return 289 | */ 290 | public String getPort() { 291 | return port; 292 | } 293 | 294 | /** 295 | * 296 | * @return 297 | */ 298 | public String getDbUserName() { 299 | return dbUserName; 300 | } 301 | 302 | /** 303 | * 304 | * @return 305 | */ 306 | public Secret getDbPassword() { 307 | return dbPassword; 308 | } 309 | 310 | /** 311 | * 312 | * @return 313 | */ 314 | public String getVersion() { 315 | return version; 316 | } 317 | 318 | /** 319 | * 320 | * @return 321 | */ 322 | public String getLoginTable() { 323 | return loginTable; 324 | } 325 | 326 | /** 327 | * 328 | * @return 329 | */ 330 | public String getUserField() { 331 | return userField; 332 | } 333 | 334 | /** 335 | * 336 | * @return 337 | */ 338 | public String getPassField() { 339 | return passField; 340 | } 341 | 342 | /** 343 | * 344 | * @return 345 | */ 346 | public String getSaltField() { 347 | return saltField; 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineUserData.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | /** 4 | * 5 | * @author Yasuyuki Saito 6 | */ 7 | public class RedmineUserData { 8 | 9 | /** */ 10 | private String username; 11 | 12 | /** */ 13 | private String password; 14 | 15 | /** */ 16 | private String salt; 17 | 18 | /** 19 | * 20 | * @return 21 | */ 22 | public String getUsername() { 23 | return username; 24 | } 25 | 26 | /** 27 | * 28 | * @param username 29 | */ 30 | public void setUsername(String username) { 31 | this.username = username; 32 | } 33 | 34 | /** 35 | * 36 | * @return 37 | */ 38 | public String getPassword() { 39 | return password; 40 | } 41 | 42 | /** 43 | * 44 | * @param password 45 | */ 46 | public void setPassword(String password) { 47 | this.password = password; 48 | } 49 | 50 | /** 51 | * 52 | * @return 53 | */ 54 | public String getSalt() { 55 | return salt; 56 | } 57 | 58 | /** 59 | * 60 | * @param salt 61 | */ 62 | public void setSalt(String salt) { 63 | this.salt = salt; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineUserDetails.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import org.acegisecurity.GrantedAuthority; 4 | import org.acegisecurity.userdetails.UserDetails; 5 | 6 | public class RedmineUserDetails implements UserDetails { 7 | 8 | /** */ 9 | private static final long serialVersionUID = 1L; 10 | 11 | private GrantedAuthority[] authorities; 12 | private String password; 13 | private String username; 14 | private boolean accountNotExpired; 15 | private boolean accountNotLocked; 16 | private boolean credentialsNotExpired; 17 | private boolean enabled; 18 | 19 | /** 20 | * 21 | * @param username 22 | * @param password 23 | * @param enabled 24 | * @param accountNonExpired 25 | * @param credentialsNonExpired 26 | * @param accountNonLocked 27 | * @param authorities 28 | */ 29 | public RedmineUserDetails(String username, String password, boolean enabled, 30 | boolean accountNonExpired, boolean credentialsNonExpired, 31 | boolean accountNonLocked, GrantedAuthority[] authorities) { 32 | this.username = username; 33 | this.password = password; 34 | this.enabled = enabled; 35 | this.accountNotExpired = accountNonExpired; 36 | this.credentialsNotExpired = credentialsNonExpired; 37 | this.accountNotLocked = accountNonLocked; 38 | this.authorities = authorities; 39 | } 40 | 41 | public GrantedAuthority[] getAuthorities() { 42 | return authorities; 43 | } 44 | 45 | public String getPassword() { 46 | return password; 47 | } 48 | 49 | public String getUsername() { 50 | return username; 51 | } 52 | 53 | public boolean isAccountNonExpired() { 54 | return accountNotExpired; 55 | } 56 | 57 | public boolean isAccountNonLocked() { 58 | return accountNotLocked; 59 | } 60 | 61 | public boolean isCredentialsNonExpired() { 62 | return credentialsNotExpired; 63 | } 64 | 65 | public boolean isEnabled() { 66 | return enabled; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/RedmineWebsiteConfig.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import hudson.Extension; 4 | import hudson.model.AbstractDescribableImpl; 5 | import hudson.model.Descriptor; 6 | import hudson.util.FormValidation; 7 | 8 | import org.kohsuke.stapler.DataBoundConstructor; 9 | import org.kohsuke.stapler.QueryParameter; 10 | 11 | /** 12 | * @since 0.14 13 | * @author ljader 14 | * 15 | */ 16 | public class RedmineWebsiteConfig extends AbstractDescribableImpl { 17 | public String name; 18 | public String baseUrl; 19 | public String versionNumber; 20 | 21 | /** 22 | * Constructor; params shouldnt be null 23 | * 24 | * @param baseUrl 25 | * @param versionNumber 26 | */ 27 | @DataBoundConstructor 28 | public RedmineWebsiteConfig(String name, String baseUrl, String versionNumber) { 29 | this.name = name; 30 | this.baseUrl = baseUrl; 31 | if (!this.baseUrl.endsWith("/")) { 32 | this.baseUrl += '/'; 33 | } 34 | this.versionNumber = versionNumber; 35 | } 36 | 37 | @Extension 38 | public static class DescriptorImpl extends Descriptor { 39 | @Override 40 | public String getDisplayName() { 41 | return ""; 42 | } 43 | 44 | public FormValidation doCheckName(@QueryParameter String name) { 45 | if (name == null || name.trim().length() < 1) { 46 | return FormValidation.error("Name can't be empty!"); 47 | } 48 | 49 | return FormValidation.ok(); 50 | } 51 | 52 | public FormValidation doCheckBaseUrl(@QueryParameter String baseUrl) { 53 | if (baseUrl == null || baseUrl.trim().length() < 1) { 54 | return FormValidation.error("Url can't be empty!"); 55 | } 56 | 57 | return FormValidation.ok(); 58 | } 59 | 60 | public FormValidation doCheckVersionNumber(@QueryParameter String versionNumber) { 61 | if (versionNumber == null || versionNumber.trim().length() < 1) { 62 | return FormValidation.ok(); 63 | } 64 | 65 | if (versionNumber.split("\\.").length != 3) { 66 | return FormValidation.error("Version number must be X.Y.Z form!"); 67 | } 68 | 69 | return FormValidation.ok(); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/VersionUtil.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | public class VersionUtil { 4 | 5 | public static boolean isVersionBefore120(String version) { 6 | if(version == null || version.isEmpty()) { 7 | return false; // null is redmine latest version 8 | } 9 | String[] versions = version.split("\\."); 10 | if(versions.length == 3 && 11 | ((Integer.valueOf(versions[0]) == 0) || 12 | (Integer.valueOf(versions[0]) == 1 && Integer.valueOf(versions[1]) < 2))) { 13 | return true; 14 | } 15 | return false; 16 | } 17 | 18 | public static boolean isVersionBefore090(String version) { 19 | if(version == null || version.isEmpty()) { 20 | return false; // null is redmine latest version 21 | } 22 | String[] versions = version.split("\\."); 23 | if(versions.length == 3 && 24 | Integer.valueOf(versions[0]) == 0 && 25 | Integer.valueOf(versions[1]) < 9) { 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | public static boolean isVersionBefore081(String version) { 32 | if(version == null || version.isEmpty()) { 33 | return false; // null is redmine latest version 34 | } 35 | String[] versions = version.split("\\."); 36 | if(versions.length == 3 && 37 | Integer.valueOf(versions[0]) == 0 && 38 | ((Integer.valueOf(versions[1]) < 8) || 39 | (Integer.valueOf(versions[1]) == 8 && Integer.valueOf(versions[2]) < 1))) { 40 | return true; 41 | } 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/dao/AbstractAuthDao.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine.dao; 2 | 3 | import hudson.plugins.redmine.RedmineAuthenticationException; 4 | import hudson.plugins.redmine.RedmineUserData; 5 | 6 | import java.sql.Connection; 7 | 8 | /** 9 | * @author Yasuyuki Saito 10 | */ 11 | public abstract class AbstractAuthDao { 12 | 13 | /** DB Connection */ 14 | protected Connection conn = null; 15 | 16 | /** 17 | * DB Connection Open. 18 | * @param dbServer DB Server 19 | * @param port Database Port 20 | * @param databaseName Database Name 21 | * @param dbUserName Database UserName 22 | * @param dbPassword Database Password 23 | * @throws RedmineAuthenticationException 24 | */ 25 | public abstract void open(String dbServer, String port, String databaseName, String dbUserName, String dbPassword) 26 | throws RedmineAuthenticationException; 27 | 28 | /** 29 | * DB Conncetion Close. 30 | */ 31 | public void close() { 32 | if (conn != null) { 33 | try { conn.close(); } catch (Exception e) {} 34 | } 35 | } 36 | 37 | /** 38 | * Table Check. 39 | * @param table 40 | * @return 41 | * @throws RedmineAuthenticationException 42 | */ 43 | public abstract boolean isTable(String table) throws RedmineAuthenticationException; 44 | 45 | /** 46 | * Field Check. 47 | * @param table 48 | * @param field 49 | * @return 50 | * @throws RedmineAuthenticationException 51 | */ 52 | public abstract boolean isField(String table, String field) throws RedmineAuthenticationException; 53 | 54 | /** 55 | * Get RedmineUserData. 56 | * @param loginTable 57 | * @param userField 58 | * @param passField 59 | * @param saltField 60 | * @param username 61 | * @return 62 | * @throws RedmineAuthenticationException 63 | */ 64 | public abstract RedmineUserData getRedmineUserData(String loginTable, String userField, String passField, String saltField, String username) throws RedmineAuthenticationException; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/dao/MySQLAuthDao.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine.dao; 2 | 3 | import hudson.plugins.redmine.RedmineAuthenticationException; 4 | import hudson.plugins.redmine.RedmineUserData; 5 | import hudson.plugins.redmine.util.Constants; 6 | 7 | import java.sql.DriverManager; 8 | import java.sql.PreparedStatement; 9 | import java.sql.ResultSet; 10 | import java.sql.SQLException; 11 | 12 | import org.apache.commons.lang.StringUtils; 13 | 14 | /** 15 | * @author Yasuyuki Saito 16 | */ 17 | public class MySQLAuthDao extends AbstractAuthDao { 18 | 19 | @Override 20 | public void open(String dbServer, String port, String databaseName, String dbUserName, String dbPassword) 21 | throws RedmineAuthenticationException { 22 | try { 23 | String connectionString = String.format(Constants.CONNECTION_STRING_FORMAT_MYSQL, dbServer, port, databaseName); 24 | 25 | Class.forName(Constants.JDBC_DRIVER_NAME_MYSQL).newInstance(); 26 | conn = DriverManager.getConnection(connectionString, dbUserName, dbPassword); 27 | } catch (SQLException e) { 28 | throw new RedmineAuthenticationException("RedmineSecurity: Connection Error", e); 29 | } catch (Exception e) { 30 | throw new RedmineAuthenticationException("RedmineSecurity: Connection Error", e); 31 | } 32 | } 33 | 34 | @Override 35 | public boolean isTable(String table) throws RedmineAuthenticationException { 36 | PreparedStatement state = null; 37 | ResultSet results = null; 38 | 39 | try { 40 | String query = "SHOW TABLES"; 41 | state = conn.prepareStatement(query); 42 | results = state.executeQuery(); 43 | 44 | if (results == null) 45 | return false; 46 | 47 | while (results.next()) { 48 | if (results.getString(1).equals(table)) 49 | return true; 50 | } 51 | 52 | return false; 53 | } catch (RedmineAuthenticationException e) { 54 | throw e; 55 | } catch (SQLException e) { 56 | throw new RedmineAuthenticationException("RedmineSecurity: Table Check Error", e); 57 | } catch (Exception e) { 58 | throw new RedmineAuthenticationException("RedmineSecurity: Table Check Error", e); 59 | } finally { 60 | if (results != null) { 61 | try { results.close(); } catch (Exception e) {} 62 | } 63 | if (state != null) { 64 | try { state.close(); } catch (Exception e) {} 65 | } 66 | } 67 | } 68 | 69 | @Override 70 | public boolean isField(String table, String field) throws RedmineAuthenticationException { 71 | PreparedStatement state = null; 72 | ResultSet results = null; 73 | 74 | try { 75 | String query = String.format("SHOW FIELDS FROM %s", table); 76 | state = conn.prepareStatement(query); 77 | results = state.executeQuery(); 78 | 79 | if (results == null) 80 | return false; 81 | 82 | while (results.next()) { 83 | if (results.getString(1).equals(field)) 84 | return true; 85 | } 86 | 87 | return false; 88 | } catch (RedmineAuthenticationException e) { 89 | throw e; 90 | } catch (SQLException e) { 91 | throw new RedmineAuthenticationException("RedmineSecurity: Field Check Error", e); 92 | } catch (Exception e) { 93 | throw new RedmineAuthenticationException("RedmineSecurity: Field Check Error", e); 94 | } finally { 95 | if (results != null) { 96 | try { results.close(); } catch (Exception e) {} 97 | } 98 | if (state != null) { 99 | try { state.close(); } catch (Exception e) {} 100 | } 101 | } 102 | } 103 | 104 | @Override 105 | public RedmineUserData getRedmineUserData(String loginTable, String userField, String passField, String saltField, String username) 106 | throws RedmineAuthenticationException { 107 | PreparedStatement state = null; 108 | ResultSet results = null; 109 | 110 | try { 111 | String query = String.format("SELECT * FROM %s WHERE %s = ?", loginTable, userField); 112 | 113 | state = conn.prepareStatement(query); 114 | state.setString(1, username); 115 | 116 | results = state.executeQuery(); 117 | 118 | if (results == null) 119 | return null; 120 | 121 | if (results.next()) { 122 | RedmineUserData userData = new RedmineUserData(); 123 | userData.setUsername(results.getString(userField)); 124 | userData.setPassword(results.getString(passField)); 125 | 126 | if (!StringUtils.isBlank(saltField)) 127 | userData.setSalt(results.getString(saltField)); 128 | 129 | return userData; 130 | } else 131 | return null; 132 | } catch (RedmineAuthenticationException e) { 133 | throw e; 134 | } catch (SQLException e) { 135 | throw new RedmineAuthenticationException("RedmineSecurity: Query Error", e); 136 | } catch (Exception e) { 137 | throw new RedmineAuthenticationException("RedmineSecurity: Query Error", e); 138 | } finally { 139 | if (results != null) { 140 | try { results.close(); } catch (Exception e) {} 141 | } 142 | if (state != null) { 143 | try { state.close(); } catch (Exception e) {} 144 | } 145 | } 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/dao/PostgreSQLAuthDao.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine.dao; 2 | 3 | import hudson.plugins.redmine.RedmineAuthenticationException; 4 | import hudson.plugins.redmine.RedmineUserData; 5 | import hudson.plugins.redmine.util.Constants; 6 | 7 | import java.sql.DriverManager; 8 | import java.sql.PreparedStatement; 9 | import java.sql.ResultSet; 10 | import java.sql.SQLException; 11 | 12 | import org.apache.commons.lang.StringUtils; 13 | 14 | /** 15 | * @author Yasuyuki Saito 16 | */ 17 | public class PostgreSQLAuthDao extends AbstractAuthDao { 18 | 19 | @Override 20 | public void open(String dbServer, String port, String databaseName, String dbUserName, String dbPassword) 21 | throws RedmineAuthenticationException { 22 | try { 23 | String connectionString = String.format(Constants.CONNECTION_STRING_FORMAT_POSTGRESQL, dbServer, port, databaseName); 24 | 25 | Class.forName(Constants.JDBC_DRIVER_NAME_POSTGRESQL).newInstance(); 26 | conn = DriverManager.getConnection(connectionString, dbUserName, dbPassword); 27 | } catch (SQLException e) { 28 | throw new RedmineAuthenticationException("RedmineSecurity: Connection Error", e); 29 | } catch (Exception e) { 30 | throw new RedmineAuthenticationException("RedmineSecurity: Connection Error", e); 31 | } 32 | } 33 | 34 | @Override 35 | public boolean isTable(String table) throws RedmineAuthenticationException { 36 | PreparedStatement state = null; 37 | ResultSet results = null; 38 | 39 | try { 40 | String query = "select tablename from pg_tables"; 41 | state = conn.prepareStatement(query); 42 | results = state.executeQuery(); 43 | 44 | if (results == null) 45 | return false; 46 | 47 | while (results.next()) { 48 | if (results.getString(1).equals(table)) 49 | return true; 50 | } 51 | 52 | return false; 53 | } catch (RedmineAuthenticationException e) { 54 | throw e; 55 | } catch (SQLException e) { 56 | throw new RedmineAuthenticationException("RedmineSecurity: Table Check Error", e); 57 | } catch (Exception e) { 58 | throw new RedmineAuthenticationException("RedmineSecurity: Table Check Error", e); 59 | } finally { 60 | if (results != null) { 61 | try { results.close(); } catch (Exception e) {} 62 | } 63 | if (state != null) { 64 | try { state.close(); } catch (Exception e) {} 65 | } 66 | } 67 | } 68 | 69 | @Override 70 | public boolean isField(String table, String field) throws RedmineAuthenticationException { 71 | PreparedStatement state = null; 72 | ResultSet results = null; 73 | 74 | try { 75 | StringBuilder query = new StringBuilder(); 76 | query.append("select column_name from information_schema.columns where table_name = ?"); 77 | 78 | state = conn.prepareStatement(query.toString()); 79 | state.setString(1, table); 80 | 81 | results = state.executeQuery(); 82 | 83 | if (results == null) 84 | return false; 85 | 86 | while (results.next()) { 87 | if (results.getString(1).equals(field)) 88 | return true; 89 | } 90 | 91 | return false; 92 | } catch (RedmineAuthenticationException e) { 93 | throw e; 94 | } catch (SQLException e) { 95 | throw new RedmineAuthenticationException("RedmineSecurity: Field Check Error", e); 96 | } catch (Exception e) { 97 | throw new RedmineAuthenticationException("RedmineSecurity: Field Check Error", e); 98 | } finally { 99 | if (results != null) { 100 | try { results.close(); } catch (Exception e) {} 101 | } 102 | if (state != null) { 103 | try { state.close(); } catch (Exception e) {} 104 | } 105 | } 106 | } 107 | 108 | @Override 109 | public RedmineUserData getRedmineUserData(String loginTable, String userField, String passField, String saltField, String username) 110 | throws RedmineAuthenticationException { 111 | PreparedStatement state = null; 112 | ResultSet results = null; 113 | 114 | try { 115 | String query = String.format("SELECT * FROM %s WHERE %s = ?", loginTable, userField); 116 | 117 | state = conn.prepareStatement(query); 118 | state.setString(1, username); 119 | 120 | results = state.executeQuery(); 121 | 122 | if (results == null) 123 | return null; 124 | 125 | if (results.next()) { 126 | RedmineUserData userData = new RedmineUserData(); 127 | userData.setUsername(results.getString(userField)); 128 | userData.setPassword(results.getString(passField)); 129 | 130 | if (!StringUtils.isBlank(saltField)) 131 | userData.setSalt(results.getString(saltField)); 132 | 133 | return userData; 134 | } else 135 | return null; 136 | } catch (RedmineAuthenticationException e) { 137 | throw e; 138 | } catch (SQLException e) { 139 | throw new RedmineAuthenticationException("RedmineSecurity: Query Error", e); 140 | } catch (Exception e) { 141 | throw new RedmineAuthenticationException("RedmineSecurity: Query Error", e); 142 | } finally { 143 | if (results != null) { 144 | try { results.close(); } catch (Exception e) {} 145 | } 146 | if (state != null) { 147 | try { state.close(); } catch (Exception e) {} 148 | } 149 | } 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/util/CipherUtil.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine.util; 2 | 3 | import java.security.MessageDigest; 4 | 5 | /** 6 | * 7 | * @author Yasuyuki Saito 8 | */ 9 | public abstract class CipherUtil { 10 | 11 | /** */ 12 | public static final String SHA1 = "SHA-1"; 13 | 14 | /** 15 | * 16 | * @param value 17 | * @return 18 | * @throws Exception 19 | */ 20 | public static String encodeSHA1(String value) throws Exception { 21 | return encode(value, SHA1); 22 | } 23 | 24 | /** 25 | * 26 | * @param value 27 | * @param algorithm 28 | * @return 29 | * @throws Exception 30 | */ 31 | public static String encode(String value, String algorithm) throws Exception { 32 | try { 33 | MessageDigest md = MessageDigest.getInstance(algorithm); 34 | md.reset(); 35 | byte[] textBytes = value.getBytes(); 36 | md.update(textBytes); 37 | byte[] digestBytes = md.digest(); 38 | 39 | StringBuilder sb = new StringBuilder(); 40 | for (byte digest : digestBytes) { 41 | sb.append(String.format("%02x", digest)); 42 | } 43 | 44 | return sb.toString(); 45 | } catch (Exception e) { 46 | throw e; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/hudson/plugins/redmine/util/Constants.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine.util; 2 | 3 | /** 4 | * @author Yasuyuki Saito 5 | */ 6 | public abstract class Constants { 7 | 8 | private Constants() {} 9 | 10 | /** Default DB Server */ 11 | public static final String DEFAULT_DB_SERVER = "127.0.0.1"; 12 | 13 | /** Default DatabaseName */ 14 | public static final String DEFAULT_DATABASE_NAME = "redmine"; 15 | 16 | /** Redmine Version 1.2.0 */ 17 | public static final String VERSION_1_2_0 = "1.2.0"; 18 | 19 | /** Redmine Version 1.1.3 */ 20 | public static final String VERSION_1_1_3 = "1.1.3"; 21 | 22 | /** Redmine Default Login Table */ 23 | public static final String DEFAULT_LOGIN_TABLE = "users"; 24 | 25 | /** Redmine Default User Field */ 26 | public static final String DEFAULT_USER_FIELD = "login"; 27 | 28 | /** Redmine Default Password Field */ 29 | public static final String DEFAULT_PASSWORD_FIELD = "hashed_password"; 30 | 31 | /** Redmine Default Salt Field */ 32 | public static final String DEFAULT_SALT_FIELD = "salt"; 33 | 34 | 35 | /** Redmine DBMS: MySQL */ 36 | public static final String DBMS_MYSQL = "MySQL"; 37 | 38 | /** Redmine DBMS: PostgreSQL */ 39 | public static final String DBMS_POSTGRESQL = "PostgreSQL"; 40 | 41 | 42 | /** Connection String Format: MySQL */ 43 | public static final String CONNECTION_STRING_FORMAT_MYSQL = "jdbc:mysql://%s:%s/%s"; 44 | 45 | /** Default Port: MySQL */ 46 | public static final String DEFAULT_PORT_MYSQL = "3306"; 47 | 48 | /** JDBC Driver Name: MySQL */ 49 | public static final String JDBC_DRIVER_NAME_MYSQL = "com.mysql.jdbc.Driver"; 50 | 51 | 52 | /** Connection String Format: PostgreSQL */ 53 | public static final String CONNECTION_STRING_FORMAT_POSTGRESQL = "jdbc:postgresql://%s:%s/%s"; 54 | 55 | /** Default Port: PostgreSQL */ 56 | public static final String DEFAULT_PORT_POSTGRESQL = "5432"; 57 | 58 | /** JDBC Driver Name: PostgreSQL */ 59 | public static final String JDBC_DRIVER_NAME_POSTGRESQL = "org.postgresql.Driver"; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/Messages.properties: -------------------------------------------------------------------------------- 1 | aggregate.redmine.ticket.metrics=Aggregate Redmine ticket metrics 2 | ticket.metrics=Ticket Metrics 3 | ticket.metrics.detail=Ticket Metrics detail 4 | error.require.redmine_url=Please set Redmine URL 5 | error.invalid.redmine_url=Invalid URL 6 | error.require.api_key=Please set API Key 7 | error.require.project_name=Please set Project Name 8 | RedmineSecurityRealm.DisplayName=Redmine User Auth 9 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/Messages_ja.properties: -------------------------------------------------------------------------------- 1 | aggregate.redmine.ticket.metrics=Redmine\u306e\u30e1\u30c8\u30ea\u30af\u30b9\u3092\u96c6\u8a08\u3059\u308b 2 | ticket.metrics=\u30c1\u30b1\u30c3\u30c8\u30e1\u30c8\u30ea\u30af\u30b9 3 | ticket.metrics.detail=\u30c1\u30b1\u30c3\u30c8\u30e1\u30c8\u30ea\u30af\u30b9\u8a73\u7d30 4 | error.require.redmine_url=Redmine URL\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044 5 | error.invalid.redmine_url=\u4e0d\u6b63\u306aURL\u3067\u3059 6 | error.require.api_key=API\u30ad\u30fc\u3092\u30bb\u30c3\u30c8\u3057\u3066\u304f\u3060\u3055\u3044 7 | error.require.project_name=\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u540d\u3092\u30bb\u30c3\u30c8\u3057\u3066\u304f\u3060\u3055\u3044 8 | RedmineSecurityRealm.DisplayName=Redmine\u30e6\u30fc\u30b6\u30fc\u8a8d\u8a3c -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/MetricsAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Ticket Metrics

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
StatusCount
${fm.status}${fm.count}
17 |
18 |
19 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/MetricsProjectAction/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 |

Ticket Metrics

3 | 4 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/MetricsProjectAction/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Ticket Metrics

5 | 6 |
7 |
8 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/config.properties: -------------------------------------------------------------------------------- 1 | API\ Key=API Key 2 | Target\ Version=Target Version 3 | Ignore\ Ticket\ Tracker=Ignore Ticket Tracker 4 | Ignore\ Ticket\ Status=Ignore Ticket Status 5 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/config_ja.properties: -------------------------------------------------------------------------------- 1 | API\ Key=API\u30ad\u30fc 2 | Target\ Version=\u30bf\u30fc\u30b2\u30c3\u30c8\u30d0\u30fc\u30b8\u30e7\u30f3 3 | Ignore\ Ticket\ Tracker=\u9664\u5916\u3059\u308b\u30c8\u30e9\u30c3\u30ab\u30fc 4 | Ignore\ Ticket\ Status=\u9664\u5916\u3059\u308b\u30b9\u30c6\u30fc\u30bf\u30b9 5 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/help-apiKey.html: -------------------------------------------------------------------------------- 1 |
2 |

Please set Redmine's API Key.

3 |

Need access to "My account > API key access" for confirm your 4 | api key.

5 |

if can't find "API access key", Redmine admin need set enable a 6 | setting. "Administration > Settings > Authentication > Enable REST 7 | web service".

8 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/help-apiKey_ja.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | RedmineのAPIキーを指定します。
APIキーはRedmineの画面から 個人設定 > APIアクセスキー 4 | で確認することができます。
APIアクセスキーの項目が出てこない場合は、 5 | 管理者権限でAPI機能がオフになっているので、Redmineの管理者に
管理 > 設定 > 認証 6 | の「RESTによるWebサービスを有効にする」をチェックして有効にしてもらうよう依頼してください。 7 |

8 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/help-ignoreTicketStatus.html: -------------------------------------------------------------------------------- 1 |
2 |

If specified, issues with this status (e.g. "Rejected", "Resolved") are skipped.

3 |

If require multi version, write comma separated.

4 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/help-ignoreTicketTracker.html: -------------------------------------------------------------------------------- 1 |
2 |

If specified, issues from this tracker (e.g. "Bug", "Feature") are skipped

3 |

If require multi version, write comma separated.

4 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/help-targetVersion.html: -------------------------------------------------------------------------------- 1 |
2 |

If specified, only issues with this version are collected.

3 |

If require multi version, write comma separated.

4 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/help-targetVersion_ja.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | メトリクス集計の対象バージョンを指定します。
カンマ区切りで複数のバージョンを合わせて集計する事もできます。
4 | 指定がない場合はプロジェクトのすべてのチケットを集計します。 5 |

6 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineMetricsPublisher/help.html: -------------------------------------------------------------------------------- 1 |
2 |

Collects ticket statistics from selected Redmine project. Statistics are displayed as a graph on job main page

3 |

Tickets can be filtered by: specified target version, ticket status, ticket tracker.

4 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineProjectProperty/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineProjectProperty/config.properties: -------------------------------------------------------------------------------- 1 | Assign\ Redmine\ project=Assign Redmine project 2 | Redmine\ website=Redmine website 3 | Redmine\ project\ identifier=Redmine project identifier 4 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineProjectProperty/global.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineProjectProperty/help-projectName.html: -------------------------------------------------------------------------------- 1 |
2 | Identifier of project in selected Redmine. It's case insensitive and will be part of URL.
3 | (For backward compatibility with previous redmine-plugin versions, also project name is checked) 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineProjectProperty/help.html: -------------------------------------------------------------------------------- 1 |
2 | Assign one of defined Redmine websites to this Jenkins project. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineRepositoryBrowser/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineRepositoryBrowser/config.properties: -------------------------------------------------------------------------------- 1 | Redmine\ repository\ identifier=Redmine repository identifier 2 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/config.jelly: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 11 |
12 | 13 | 19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 45 |
46 | 47 | 53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/config.properties: -------------------------------------------------------------------------------- 1 | DBMS=Redmine DBMS 2 | DBMS.MySQL=MySQL 3 | DBMS.PostgreSQL=PostgreSQL 4 | Server=Server 5 | Database=Database 6 | Port=Port 7 | Username=Username 8 | Password=Password 9 | RedmineVersion=Redmine Version 10 | Version.1_2_0=1.2.0 11 | Version.1_1_3=1.1.3 12 | LoginTable=Login Table 13 | UserField=User Field 14 | PassField=Password Field 15 | SaltField=Salt Field 16 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/config_ja.properties: -------------------------------------------------------------------------------- 1 | DBMS=Redmine DBMS 2 | DBMS.MySQL=MySQL 3 | DBMS.PostgreSQL=PostgreSQL 4 | Server=DB\u30b5\u30fc\u30d0\u30fc 5 | Database=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d 6 | Port=\u30dd\u30fc\u30c8\u756a\u53f7 7 | Username=DB\u30e6\u30fc\u30b6\u30fc 8 | Password=DB\u30d1\u30b9\u30ef\u30fc\u30c9 9 | RedmineVersion=Redmine\u30d0\u30fc\u30b8\u30e7\u30f3 10 | Version.1_2_0=1.2.0\u4ee5\u4e0a 11 | Version.1_1_3=1.1.3\u4ee5\u4e0b 12 | LoginTable=\u30ed\u30b0\u30a4\u30f3\u7ba1\u7406\u30c6\u30fc\u30d6\u30eb 13 | UserField=\u30ed\u30b0\u30a4\u30f3\u30e6\u30fc\u30b6\u30fc\u5217 14 | PassField=\u30ed\u30b0\u30a4\u30f3\u30d1\u30b9\u30ef\u30fc\u30c9\u5217 15 | SaltField=Salt\u5217(Ver.1.2.0\u4ee5\u4e0a) -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-databaseName.html: -------------------------------------------------------------------------------- 1 |
2 | Redmine Database Name. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-databaseName_ja.html: -------------------------------------------------------------------------------- 1 |
2 | DBサーバー上のデータベースを指定して下さい。 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-dbPassword.html: -------------------------------------------------------------------------------- 1 |
2 | Specify the password required to authenticate against the database. 3 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-dbPassword_ja.html: -------------------------------------------------------------------------------- 1 |
2 | DBのログインパスワードを指定して下さい。 3 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-dbServer.html: -------------------------------------------------------------------------------- 1 |
2 | DB Server. 3 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-dbServer_ja.html: -------------------------------------------------------------------------------- 1 |
2 |

DBサーバーを指定して下さい。

3 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-dbUserName.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | DB User Name. 4 |

5 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-dbUserName_ja.html: -------------------------------------------------------------------------------- 1 |
2 | DBのログインユーザーを指定して下さい。 3 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-loginTable.html: -------------------------------------------------------------------------------- 1 |
2 | Redmine Login Table. 3 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-loginTable_ja.html: -------------------------------------------------------------------------------- 1 |
2 | Redmineのログインテーブルを指定します。
3 | この値を指定しない場合は、「users」が設定されます。 4 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-passField.html: -------------------------------------------------------------------------------- 1 |
2 | Redmine Password Field. 3 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-passField_ja.html: -------------------------------------------------------------------------------- 1 |
2 | Redmineのログインテーブルのパスワード列を指定します。
3 | この値を指定しない場合は、「hashed_password」が設定されます。 4 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-port.html: -------------------------------------------------------------------------------- 1 |
2 | DB port.
3 | MySQL default port 3306.
4 | Postgre SQL default port 5432. 5 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-port_ja.html: -------------------------------------------------------------------------------- 1 |
2 | DBのポートを入力してください。
3 | 入力しない場合、DBMSがMySQLの場合は「3306」がデフォルトで指定されます。
4 | また、DBMSがPostgreSQLの場合、「5432」がデフォルトで指定されます。 5 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-saltField.html: -------------------------------------------------------------------------------- 1 |
2 |

Salt Field.

3 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-saltField_ja.html: -------------------------------------------------------------------------------- 1 |
2 | RedmineのログインテーブルのSalt列を指定します。
3 | Redmineバージョンが「1.2.0以上」を指定した場合、有効です。
4 | この値を指定しない場合は、「salt」が設定されます。 5 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-userField.html: -------------------------------------------------------------------------------- 1 |
2 | Redmine Login User Field. 3 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-userField_ja.html: -------------------------------------------------------------------------------- 1 |
2 | RedmineのログインテーブルのユーザーID列を指定します。
3 | この値を指定しない場合は、「login」が設定されます。 4 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-version.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Select Redmine version.
4 |

5 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineSecurityRealm/help-version_ja.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Redmineのバージョンを指定してください。
4 | バージョンが1.2.0以上か1.1.3以下のどちらかを指定してください。 5 |

6 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineWebsiteConfig/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 |
-------------------------------------------------------------------------------- /src/main/resources/hudson/plugins/redmine/RedmineWebsiteConfig/config.properties: -------------------------------------------------------------------------------- 1 | Name=Name 2 | Base\ url=Base url 3 | Version\ number=Version number 4 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 |
2 | This plugin integrates Redmine project management 3 | into Jenkins. 4 |
5 | -------------------------------------------------------------------------------- /src/main/webapp/help-auth-overview.html: -------------------------------------------------------------------------------- 1 |
2 |

Redmine User Auth module connects to Redmine database to authenticate users.

3 |

Password entered during Jenkins login is hashed, and compared with hash stored in database in "passField".
4 | Hash algorithm depends on Redmine version:
5 |

    6 |
  1. in Redmine 1.2.0 and above, the algorithm is: SHA1(salt + SHA1(clear_text_passwd))
  2. 7 |
  3. in Redmine 1.1.x and lower, there is no salt, so the algorithm is: SHA1(clear_text_passwd)
  4. 8 |

9 |
-------------------------------------------------------------------------------- /src/main/webapp/help-auth-overview_ja.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Redmineのユーザーを使用して認証を行います。 4 |

5 |
-------------------------------------------------------------------------------- /src/main/webapp/help-global.html: -------------------------------------------------------------------------------- 1 |
2 | Set the URL of Redmine that projects on Jenkins uses, such as http://myhost/redmine/ 3 |
-------------------------------------------------------------------------------- /src/main/webapp/help-repo.html: -------------------------------------------------------------------------------- 1 |
2 | If the Redmine repository is not the project's default repository, specify the identifier. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-version.html: -------------------------------------------------------------------------------- 1 |
2 | Specify redmine version (e.g. 1.2.0) for compatibility, leave empty for redmine latest version. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/redmine-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/redmine-plugin/27ba4e65308432c6ec104e96812ebccd8b78d1a1/src/main/webapp/redmine-logo.png -------------------------------------------------------------------------------- /src/main/webapp/ruby-logo-R.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/redmine-plugin/27ba4e65308432c6ec104e96812ebccd8b78d1a1/src/main/webapp/ruby-logo-R.png -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/redmine/RedmineLinkAnnotatorTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import hudson.MarkupText; 4 | import hudson.plugins.redmine.RedmineLinkAnnotator; 5 | import junit.framework.TestCase; 6 | 7 | public class RedmineLinkAnnotatorTest extends TestCase { 8 | 9 | private static final String REDMINE_URL = "http://local.redmine/"; 10 | 11 | public void testWikiLinkSyntax() { 12 | assertAnnotatedTextEquals("Nothing here.", "Nothing here."); 13 | assertAnnotatedTextEquals("Text with WikiLink.", "Text with WikiLink."); 14 | assertAnnotatedTextEquals("#42", "#42"); 15 | assertAnnotatedTextEquals("IssueID 22", "IssueID 22"); 16 | assertAnnotatedTextEquals("fixes 10,11,12", 17 | "fixes 10," + 18 | "11," + 19 | "12"); 20 | assertAnnotatedTextEquals("references 110,111,112,113", 21 | "references 110," + 22 | "111," + 23 | "112," + 24 | "113"); 25 | assertAnnotatedTextEquals("closes 210, 211", 26 | "closes 210, " + 27 | "211"); 28 | assertAnnotatedTextEquals("closes 210 211", 29 | "closes 210 " + 30 | "211"); 31 | assertAnnotatedTextEquals("refs 310, 11, 4, 4120", 32 | "refs 310, " + 33 | "11, " + 34 | "4, " + 35 | "4120"); 36 | assertAnnotatedTextEquals("refs 1&11&111&1111", 37 | "refs 1&" + 38 | "11&" + 39 | "111&" + 40 | "1111"); 41 | assertAnnotatedTextEquals("IssueID 21&11&100", 42 | "IssueID 21&" + 43 | "11&" + 44 | "100"); 45 | assertAnnotatedTextEquals("refs #1,#11,#111,#1111", 46 | "refs #1," + 47 | "#11," + 48 | "#111," + 49 | "#1111"); 50 | assertAnnotatedTextEquals("refs #1, #11, #111,#1111", 51 | "refs #1, " + 52 | "#11, " + 53 | "#111," + 54 | "#1111"); 55 | assertAnnotatedTextEquals("refs #1", 56 | "refs #1"); 57 | assertAnnotatedTextEquals("closes #1 ", 58 | "closes #1&" + 59 | "#11"); 60 | assertAnnotatedTextEquals("closes #1", 61 | "closes #1"); 62 | assertAnnotatedTextEquals("IssueID #1 #11", 63 | "IssueID #1 " + 64 | "#11"); 65 | } 66 | 67 | private void assertAnnotatedTextEquals(String originalText, String expectedAnnotatedText) { 68 | MarkupText markupText = new MarkupText(originalText); 69 | for (RedmineLinkAnnotator.LinkMarkup markup : RedmineLinkAnnotator.MARKUPS) { 70 | markup.process(markupText, REDMINE_URL); 71 | } 72 | 73 | System.out.println(markupText.toString(true)); 74 | assertEquals(expectedAnnotatedText, markupText.toString(true)); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/redmine/RedmineMetricsCalculatorTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import mockit.NonStrictExpectations; 11 | 12 | import org.junit.Test; 13 | 14 | import com.taskadapter.redmineapi.RedmineException; 15 | import com.taskadapter.redmineapi.RedmineManager; 16 | import com.taskadapter.redmineapi.bean.Issue; 17 | import com.taskadapter.redmineapi.bean.Project; 18 | import com.taskadapter.redmineapi.bean.Version; 19 | 20 | public class RedmineMetricsCalculatorTest { 21 | 22 | @Test 23 | public void testCalc() throws MetricsException, RedmineException { 24 | new NonStrictExpectations() { 25 | RedmineManager redmineManager; 26 | { 27 | redmineManager.getProjects(); 28 | ArrayList projects = new ArrayList(); 29 | Project p = new Project(); 30 | p.setId(1); 31 | p.setName("Example"); 32 | projects.add(p); 33 | returns(projects); 34 | 35 | redmineManager.getVersions(p.getId()); 36 | ArrayList versions = new ArrayList(); 37 | Version v = new Version(); 38 | v.setId(1); 39 | v.setName("v1"); 40 | versions.add(v); 41 | returns(versions); 42 | 43 | Map params = new HashMap(); 44 | params.put("project_id", p.getId().toString()); 45 | params.put("fixed_version_id", "1"); 46 | params.put("status_id", "*"); 47 | redmineManager.getIssues(params); 48 | List issues = new ArrayList(); 49 | Issue issue = new Issue(); 50 | issue.setStatusId(1); 51 | issue.setId(1); 52 | issue.setSubject("Hello"); 53 | issue.setStatusName("Open"); 54 | issues.add(issue); 55 | returns(issues); 56 | } 57 | }; 58 | 59 | RedmineMetricsCalculator rmc = new RedmineMetricsCalculator( 60 | "http://example.com/", "APIKEY", "Example", "v1", "", ""); 61 | assertEquals(1, rmc.calc().size()); 62 | } 63 | 64 | @Test(expected=MetricsException.class) 65 | public void testNoSuchProject() throws MetricsException, RedmineException { 66 | new NonStrictExpectations() { 67 | RedmineManager redmineManager; 68 | { 69 | redmineManager.getProjects(); 70 | ArrayList projects = new ArrayList(); 71 | Project p = new Project(); 72 | p.setId(1); 73 | p.setName("Example"); 74 | projects.add(p); 75 | returns(projects); 76 | } 77 | }; 78 | 79 | RedmineMetricsCalculator rmc = new RedmineMetricsCalculator( 80 | "http://example.com/", "APIKEY", "NoSuchProject", "v1", "", ""); 81 | rmc.calc(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/hudson/plugins/redmine/VersionUtilTest.java: -------------------------------------------------------------------------------- 1 | package hudson.plugins.redmine; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | public class VersionUtilTest { 8 | 9 | @Test 10 | public void testIsVersionBefore120() { 11 | assertFalse(VersionUtil.isVersionBefore120("")); 12 | 13 | assertTrue(VersionUtil.isVersionBefore120("0.9.0")); 14 | assertTrue(VersionUtil.isVersionBefore120("1.0.0")); 15 | assertTrue(VersionUtil.isVersionBefore120("1.1.1")); 16 | assertTrue(VersionUtil.isVersionBefore120("1.1.9")); 17 | 18 | assertFalse(VersionUtil.isVersionBefore120("1.2.0")); 19 | assertFalse(VersionUtil.isVersionBefore120("2.0.0")); 20 | } 21 | 22 | @Test 23 | public void testIsVersionBefore090() { 24 | assertFalse(VersionUtil.isVersionBefore090("")); 25 | 26 | assertTrue(VersionUtil.isVersionBefore090("0.8.0")); 27 | assertTrue(VersionUtil.isVersionBefore090("0.8.7")); 28 | 29 | assertFalse(VersionUtil.isVersionBefore090("0.9.0")); 30 | assertFalse(VersionUtil.isVersionBefore081("1.0.0")); 31 | assertFalse(VersionUtil.isVersionBefore081("2.0.0")); 32 | } 33 | 34 | @Test 35 | public void testIsVersionBefore081() { 36 | assertFalse(VersionUtil.isVersionBefore081("")); 37 | 38 | assertTrue(VersionUtil.isVersionBefore081("0.2.0")); 39 | assertTrue(VersionUtil.isVersionBefore081("0.7.9")); 40 | assertTrue(VersionUtil.isVersionBefore081("0.8.0")); 41 | 42 | assertFalse(VersionUtil.isVersionBefore081("0.8.2")); 43 | assertFalse(VersionUtil.isVersionBefore081("1.2.0")); 44 | assertFalse(VersionUtil.isVersionBefore081("1.8.0")); 45 | assertFalse(VersionUtil.isVersionBefore081("2.0.0")); 46 | } 47 | 48 | } 49 | --------------------------------------------------------------------------------