Skip to content

Commit

Permalink
initial bitbucket support (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
monitorjbl committed Nov 13, 2015
1 parent 980ba1a commit c025d3e
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 98 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Once you've got the plugin installed, any user can log in and go to their accoun
TOKEN=<paste token here>
curl -H "X-Auth-User:admin" \
-H "X-Auth-Token:$TOKEN" \
http://localhost:7990/stash/rest/api/1.0/projects/PROJECT_1/repos
http://localhost:7990/bitbucket/rest/api/1.0/projects/PROJECT_1/repos
```

Expand Down
50 changes: 25 additions & 25 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@
<packaging>atlassian-plugin</packaging>

<name>Token Authenticator</name>
<description>Enables token-based authentication to the Stash REST API</description>
<description>Enables token-based authentication to the Bitbucket REST API</description>
<organization>
<name>Taylor Jones</name>
<url>http://monitorjbl.github.io</url>
</organization>


<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.atlassian.stash</groupId>
<artifactId>stash-parent</artifactId>
<version>${stash.version}</version>
<groupId>com.atlassian.bitbucket.server</groupId>
<artifactId>bitbucket-parent</artifactId>
<version>${bitbucket.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand All @@ -36,20 +35,20 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.stash</groupId>
<artifactId>stash-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.stash</groupId>
<artifactId>stash-spi</artifactId>
<groupId>com.atlassian.bitbucket.server</groupId>
<artifactId>bitbucket-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.stash</groupId>
<artifactId>stash-page-objects</artifactId>
<groupId>com.atlassian.bitbucket.server</groupId>
<artifactId>bitbucket-spi</artifactId>
<scope>provided</scope>
</dependency>
<!--<dependency>-->
<!--<groupId>com.atlassian.stash</groupId>-->
<!--<artifactId>stash-page-objects</artifactId>-->
<!--<scope>provided</scope>-->
<!--</dependency>-->
<dependency>
<groupId>com.atlassian.templaterenderer</groupId>
<artifactId>atlassian-template-renderer-api</artifactId>
Expand All @@ -58,7 +57,7 @@
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -104,16 +103,16 @@
<plugins>
<plugin>
<groupId>com.atlassian.maven.plugins</groupId>
<artifactId>maven-stash-plugin</artifactId>
<artifactId>bitbucket-maven-plugin</artifactId>
<version>${amps.version}</version>
<extensions>true</extensions>
<configuration>
<products>
<product>
<id>stash</id>
<instanceId>stash</instanceId>
<version>${stash.version}</version>
<dataVersion>${stash.data.version}</dataVersion>
<id>bitbucket</id>
<instanceId>bitbucket</instanceId>
<version>${bitbucket.version}</version>
<dataVersion>${bitbucket.data.version}</dataVersion>
</product>
</products>
</configuration>
Expand All @@ -123,17 +122,18 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

<properties>
<stash.version>3.9.1</stash.version>
<stash.data.version>3.5.1</stash.data.version>
<amps.version>5.0.13</amps.version>
<bitbucket.version>4.0.2</bitbucket.version>
<bitbucket.data.version>4.0.2</bitbucket.data.version>
<amps.version>6.1.0</amps.version>
<plugin.testrunner.version>1.2.3</plugin.testrunner.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.thundermoose.plugins;

import com.atlassian.stash.i18n.KeyedMessage;
import com.atlassian.stash.user.AuthenticationException;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.auth.AuthenticationException;

public class TokenAuthenticationException extends AuthenticationException {
public TokenAuthenticationException(KeyedMessage message) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.thundermoose.plugins;

import com.atlassian.stash.auth.HttpAuthenticationContext;
import com.atlassian.stash.auth.HttpAuthenticationHandler;
import com.atlassian.stash.i18n.I18nKey;
import com.atlassian.stash.i18n.I18nService;
import com.atlassian.stash.user.StashUser;
import com.atlassian.stash.user.UserService;
import com.atlassian.bitbucket.auth.HttpAuthenticationContext;
import com.atlassian.bitbucket.auth.HttpAuthenticationHandler;
import com.atlassian.bitbucket.i18n.I18nKey;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.UserService;
import com.thundermoose.plugins.admin.AdminConfig;
import com.thundermoose.plugins.admin.AdminConfigDao;
import com.thundermoose.plugins.paths.PathMatcher;
Expand All @@ -14,14 +14,16 @@
import com.thundermoose.plugins.utils.Encrypter;
import com.thundermoose.plugins.utils.EncryptionException;
import com.thundermoose.plugins.utils.Utils;
import org.apache.commons.codec.binary.Base64;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Objects;
import java.util.TimeZone;

import static org.apache.commons.lang3.StringUtils.isNotEmpty;

Expand Down Expand Up @@ -52,14 +54,14 @@ public void validateAuthentication(HttpAuthenticationContext httpAuthenticationC

@Nullable
@Override
public StashUser authenticate(HttpAuthenticationContext ctx) {
public ApplicationUser authenticate(HttpAuthenticationContext ctx) {
HttpServletRequest request = ctx.getRequest();
String username = request.getHeader(USER_HEADER);
String token = request.getHeader(TOKEN_HEADER);
String path = request.getRequestURI().replaceFirst(request.getContextPath(), "");

if (isNotEmpty(username) && isNotEmpty(token) && path.startsWith("/rest/")) {
if (isTokenValid(path, username, token)) {
if(isNotEmpty(username) && isNotEmpty(token) && path.startsWith("/rest/")) {
if(isTokenValid(path, username, token)) {
return userService.getUserByName(username);
}
}
Expand All @@ -70,32 +72,32 @@ public StashUser authenticate(HttpAuthenticationContext ctx) {
boolean isTokenValid(String path, String username, String token) {
try {
AdminConfig config = adminDao.getAdminConfig();
if (!config.getEnabled()) {
if(!config.getEnabled()) {
return false;
}

Encrypter encrypter = new Encrypter(Base64.decodeBase64(config.getKey()));
Encrypter encrypter = new Encrypter(Base64.getDecoder().decode(config.getKey()));
String unencrypted = encrypter.decrypt(token);
String[] split = unencrypted.split(":");
if (split.length != 4) {
if(split.length != 4) {
//not a valid token
return false;
}

Integer ttl = adminDao.getAdminConfig().getTtl();
DateTime expiry = new DateTime(Long.parseLong(split[1])).plusDays(ttl);
if (Objects.equals(split[0], username) && (ttl <= 0 || DateTime.now().isBefore(expiry))) {
LocalDateTime expiry = LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(split[1])), TimeZone.getDefault().toZoneId()).plusDays(ttl);
if(Objects.equals(split[0], username) && (ttl <= 0 || LocalDateTime.now().isBefore(expiry))) {
//token is valid, see if the path is allowed token access by admin
return new PathMatcher(
config.getAdminPaths(),
config.getProjectPaths(),
config.getRepoPaths()
).pathAllowed(path) && Objects.equals(userDao.getUserConfig(username).getToken(), token);
} else if (Objects.equals(split[0], username) && DateTime.now().isAfter(expiry)) {
} else if(Objects.equals(split[0], username) && LocalDateTime.now().isAfter(expiry)) {
//token is expired, generate a new one
userDao.setUserConfig(username, new UserConfig(encrypter.encrypt(utils.generateTokenForUser(username, config.getTtl()))));
}
} catch (EncryptionException e) {
} catch(EncryptionException e) {
log.debug("Could not decrypt provided token", e);
throw new TokenAuthenticationException(i18nService.getKeyedText(new I18nKey("auth.exception.message")));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import com.thundermoose.plugins.admin.AdminConfigDao;
import com.thundermoose.plugins.utils.Encrypter;
import com.thundermoose.plugins.utils.Utils;
import org.apache.commons.codec.binary.Base64;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
Expand All @@ -14,6 +13,7 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Base64;

@Path("/user")
public class UserConfigResource {
Expand Down Expand Up @@ -54,7 +54,7 @@ public Response regenerateToken(@Context HttpServletRequest request) {
}

private String generateEncryptedToken(String username) {
Encrypter encrypter = new Encrypter(Base64.decodeBase64(adminDao.getAdminConfig().getKey()));
Encrypter encrypter = new Encrypter(Base64.getDecoder().decode(adminDao.getAdminConfig().getKey()));
return encrypter.encrypt(utils.generateTokenForUser(username, adminDao.getAdminConfig().getTtl()));
}

Expand Down
7 changes: 3 additions & 4 deletions src/main/java/com/thundermoose/plugins/utils/Encrypter.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.thundermoose.plugins.utils;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
Expand All @@ -11,6 +9,7 @@
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class Encrypter {
private Cipher ecipher;
Expand All @@ -34,15 +33,15 @@ public String encrypt(String value) throws EncryptionException {
try {
byte[] utf8 = value.getBytes("UTF8");
byte[] enc = ecipher.doFinal(utf8);
return new String(Base64.encodeBase64(enc));
return new String(Base64.getEncoder().encode(enc));
} catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException e) {
throw new EncryptionException("Could not encrypt string", e);
}
}

public String decrypt(String value) throws EncryptionException {
try {
byte[] dec = Base64.decodeBase64(value.getBytes());
byte[] dec = Base64.getDecoder().decode(value.getBytes());
byte[] utf8 = dcipher.doFinal(dec);
return new String(utf8, "UTF8");
} catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package com.thundermoose.plugins.utils;

import org.apache.commons.codec.binary.Base64;

import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class KeyGenerator {
public String generateKey() {
try {
return new String(Base64.encodeBase64(javax.crypto.KeyGenerator.getInstance("AES").generateKey().getEncoded()));
return new String(Base64.getEncoder().encode(javax.crypto.KeyGenerator.getInstance("AES").generateKey().getEncoded()));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/com/thundermoose/plugins/utils/Utils.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.thundermoose.plugins.utils;

import com.atlassian.sal.api.auth.LoginUriProvider;
import org.joda.time.DateTime;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.UUID;

public class Utils {
Expand All @@ -22,7 +22,7 @@ public void redirectToLogin(HttpServletRequest request, HttpServletResponse resp

public URI getUri(HttpServletRequest request) {
StringBuffer builder = request.getRequestURL();
if (request.getQueryString() != null) {
if(request.getQueryString() != null) {
builder.append("?");
builder.append(request.getQueryString());
}
Expand All @@ -43,20 +43,20 @@ public URI getUri(HttpServletRequest request) {
* @return
*/
public String generateTokenForUser(String username, Integer ttl) {
return username + ":" + System.currentTimeMillis() + ":" + DateTime.now().plusDays(ttl).getMillis() + ":" + UUID.randomUUID().toString();
return username + ":" + System.currentTimeMillis() + ":" + ZonedDateTime.now().plusDays(ttl).toInstant().toEpochMilli() + ":" + UUID.randomUUID().toString();
}

public static String createRegexFromGlob(String line) {
if (line.endsWith("**")) {
if(line.endsWith("**")) {
String chopped = line.substring(0, line.length() - 2);
if (!chopped.endsWith("/")) {
if(!chopped.endsWith("/")) {
throw new IllegalArgumentException("Glob operator must be preceeded by a '/' character");
}
String single = regexWildcard(escapeRegexCharacters(chopped.substring(0, chopped.length() - 1)));
return regexWildcard(escapeRegexCharacters(chopped)) + ".*|" + single;
} else {
//can't support globs that aren't at the end, make sure there aren't any
if (line.replaceAll("(\\*\\*)", "").length() != line.length()) {
if(line.replaceAll("(\\*\\*)", "").length() != line.length()) {
throw new IllegalArgumentException("Glob operator (**) can only be at the end of a path");
}
return regexWildcard(escapeRegexCharacters(line));
Expand Down
6 changes: 3 additions & 3 deletions src/main/resources/atlassian-plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<param name="plugin-logo">images/pluginIcon.png</param>
</plugin-info>

<component-import key="i18nService" interface="com.atlassian.stash.i18n.I18nService"/>
<component-import key="userService" interface="com.atlassian.stash.user.UserService"/>
<component-import key="i18nService" interface="com.atlassian.bitbucket.i18n.I18nService"/>
<component-import key="userService" interface="com.atlassian.bitbucket.user.UserService"/>
<component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties"/>
<component-import key="userManager" interface="com.atlassian.sal.api.user.UserManager"/>
<component-import key="loginUriProvider" interface="com.atlassian.sal.api.auth.LoginUriProvider"/>
Expand Down Expand Up @@ -49,7 +49,7 @@
<description>Provides REST resources for the admin UI.</description>
</rest>

<web-item key="account-token-tab" name="Account navigation tab" section="stash.user.account.nav" weight="100">
<web-item key="account-token-tab" name="Account navigation tab" section="bitbucket.user.account.nav" weight="100">
<label>Token</label>
<link>/plugins/servlet/auth-token/user</link>
<tooltip>Token-based Authentication</tooltip>
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/user.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html>
<head>
<title>$i18n.getText("user.title")</title>
<meta name="decorator" content="stash.users.account"/>
<meta name="decorator" content="bitbucket.users.account"/>
$webResourceManager.requireResource("com.thundermoose.plugins.stash-token-auth:resources")
<meta name="application-base-url" content="$applicationProperties.getBaseUrl()">
<meta name="activeTab" content="account-plugin-tab">
Expand Down
Loading

0 comments on commit c025d3e

Please sign in to comment.