PageRenderTime 29ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/plugin/src/main/java/com/atlassian/plugin/remotable/plugin/module/applinks/RemotePluginContainerModuleDescriptor.java

https://bitbucket.org/rodogu/remotable-plugins
Java | 319 lines | 259 code | 31 blank | 29 comment | 26 complexity | 6d86af4a305ff1a99529e2c3e5a2db4b MD5 | raw file
  1. package com.atlassian.plugin.remotable.plugin.module.applinks;
  2. import com.atlassian.applinks.api.ApplicationId;
  3. import com.atlassian.applinks.api.ApplicationLink;
  4. import com.atlassian.applinks.api.TypeNotInstalledException;
  5. import com.atlassian.applinks.spi.application.ApplicationIdUtil;
  6. import com.atlassian.applinks.spi.link.ApplicationLinkDetails;
  7. import com.atlassian.applinks.spi.link.MutatingApplicationLinkService;
  8. import com.atlassian.applinks.spi.util.TypeAccessor;
  9. import com.atlassian.oauth.Consumer;
  10. import com.atlassian.oauth.ServiceProvider;
  11. import com.atlassian.oauth.serviceprovider.ServiceProviderConsumerStore;
  12. import com.atlassian.oauth.util.RSAKeys;
  13. import com.atlassian.plugin.Plugin;
  14. import com.atlassian.plugin.PluginInformation;
  15. import com.atlassian.plugin.PluginParseException;
  16. import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
  17. import com.atlassian.plugin.descriptors.CannotDisable;
  18. import com.atlassian.plugin.module.ModuleFactory;
  19. import com.atlassian.plugin.remotable.host.common.util.BundleUtil;
  20. import com.atlassian.plugin.remotable.plugin.OAuthLinkManager;
  21. import com.atlassian.plugin.remotable.plugin.PermissionManager;
  22. import com.atlassian.plugin.remotable.plugin.util.OsgiServiceUtils;
  23. import com.atlassian.plugin.remotable.plugin.util.RemotePluginUtil;
  24. import com.atlassian.plugin.remotable.spi.Permissions;
  25. import com.atlassian.plugin.remotable.spi.applinks.RemotePluginContainerApplicationType;
  26. import com.atlassian.sal.api.pluginsettings.PluginSettings;
  27. import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
  28. import com.atlassian.util.concurrent.NotNull;
  29. import org.dom4j.Element;
  30. import org.osgi.framework.Bundle;
  31. import org.osgi.framework.BundleContext;
  32. import org.slf4j.Logger;
  33. import org.slf4j.LoggerFactory;
  34. import java.net.URI;
  35. import java.security.GeneralSecurityException;
  36. import java.security.PublicKey;
  37. import java.util.List;
  38. import static com.atlassian.plugin.remotable.spi.util.Dom4jUtils.getOptionalAttribute;
  39. import static com.atlassian.plugin.remotable.spi.util.Dom4jUtils.getRequiredElementText;
  40. import static com.atlassian.plugin.remotable.spi.util.Dom4jUtils.getRequiredUriAttribute;
  41. import static com.google.common.base.Preconditions.checkNotNull;
  42. /**
  43. * Dynamically creates an application link for a plugin host
  44. */
  45. @CannotDisable
  46. public final class RemotePluginContainerModuleDescriptor extends AbstractModuleDescriptor<Void>
  47. {
  48. public static final String PLUGIN_KEY_PROPERTY = "plugin-key";
  49. private final MutatingApplicationLinkService applicationLinkService;
  50. private final OAuthLinkManager oAuthLinkManager;
  51. private final PermissionManager permissionManager;
  52. private final TypeAccessor typeAccessor;
  53. private final BundleContext bundleContext;
  54. private final PluginSettingsFactory pluginSettingsFactory;
  55. private static final Logger log = LoggerFactory.getLogger(RemotePluginContainerModuleDescriptor.class);
  56. private URI displayUrl;
  57. private Element oauthElement;
  58. private ApplicationLinkDetails applicationLinkDetails;
  59. private boolean remoteMode;
  60. private Bundle pluginBundle;
  61. public RemotePluginContainerModuleDescriptor(
  62. ModuleFactory moduleFactory,
  63. MutatingApplicationLinkService applicationLinkService,
  64. OAuthLinkManager oAuthLinkManager,
  65. PermissionManager permissionManager,
  66. TypeAccessor typeAccessor,
  67. BundleContext bundleContext,
  68. PluginSettingsFactory pluginSettingsFactory)
  69. {
  70. super(moduleFactory);
  71. this.applicationLinkService = checkNotNull(applicationLinkService);
  72. this.oAuthLinkManager = checkNotNull(oAuthLinkManager);
  73. this.permissionManager = checkNotNull(permissionManager);
  74. this.typeAccessor = checkNotNull(typeAccessor);
  75. this.bundleContext = checkNotNull(bundleContext);
  76. this.pluginSettingsFactory = checkNotNull(pluginSettingsFactory);
  77. }
  78. @Override
  79. public void init(@NotNull Plugin plugin, @NotNull Element element) throws PluginParseException
  80. {
  81. super.init(plugin, element);
  82. this.oauthElement = element.element("oauth");
  83. this.displayUrl = getRequiredUriAttribute(element, "display-url");
  84. this.applicationLinkDetails = ApplicationLinkDetails.builder()
  85. .displayUrl(displayUrl)
  86. .isPrimary(false)
  87. // todo: support i18n names
  88. .name(plugin.getName() != null ? plugin.getName() : plugin.getKey())
  89. .rpcUrl(displayUrl)
  90. .build();
  91. if (element.getParent().elements(element.getName()).size() > 1)
  92. {
  93. throw new PluginParseException("Can only have one remote-plugin-container module in a descriptor");
  94. }
  95. this.remoteMode = RemotePluginUtil.isRemoteMode(BundleUtil.findBundleForPlugin(bundleContext, plugin.getKey()));
  96. }
  97. @Override
  98. public void enabled()
  99. {
  100. this.pluginBundle = BundleUtil.findBundleForPlugin(bundleContext, getPluginKey());
  101. if (remoteMode)
  102. {
  103. final ApplicationId expectedApplicationId = ApplicationIdUtil.generate(displayUrl);
  104. ApplicationLink link = null;
  105. final RemotePluginContainerApplicationType applicationType = typeAccessor.getApplicationType(
  106. RemotePluginContainerApplicationType.class);
  107. try
  108. {
  109. link = applicationLinkService.getApplicationLink(expectedApplicationId);
  110. }
  111. catch (TypeNotInstalledException ex)
  112. {
  113. log.info("Link found for '{}' but the type cannot be found, treating as not found", getPluginKey());
  114. manuallyDeleteApplicationId(expectedApplicationId);
  115. }
  116. // @todo this should work but it just fucks stuff up beyond belief (instead of lines 160-170)
  117. // try to find link with old display url
  118. // for (ApplicationLink otherLink : applicationLinkService.getApplicationLinks(RemotePluginContainerApplicationType.class))
  119. // {
  120. // if (getPluginKey().equals(otherLink.getProperty(PLUGIN_KEY_PROPERTY)) && (link == null || link.getId().get().equals(otherLink.getId().get())))
  121. // {
  122. // log.debug("Old application link for this plugin '{}' found with different display url '{}', removing",
  123. // getPluginKey(), displayUrl);
  124. // applicationLinkService.deleteApplicationLink(otherLink);
  125. // }
  126. // }
  127. if (link != null)
  128. {
  129. if (getPluginKey().equals(link.getProperty(PLUGIN_KEY_PROPERTY)))
  130. {
  131. log.info("Application link for remote plugin container '{}' already exists", getPluginKey());
  132. }
  133. // @todo this should work but it just fucks stuff up beyond belief (instead of lines 160-170)
  134. // else if (link.getProperty(PLUGIN_KEY_PROPERTY) == null)
  135. // {
  136. // log.warn("Found application link for url '{}' is missing associated plugin key", displayUrl);
  137. // link.putProperty(PLUGIN_KEY_PROPERTY, getPluginKey());
  138. // }
  139. else
  140. {
  141. throw new PluginParseException("Application link already exists for id '" + expectedApplicationId + "' but it isn't the target " +
  142. " plugin '" + getPluginKey() + "': unexpected plugin key is: " + link.getProperty(PLUGIN_KEY_PROPERTY));
  143. }
  144. }
  145. else
  146. {
  147. // try to find link with old display url
  148. for (ApplicationLink otherLink : applicationLinkService.getApplicationLinks(RemotePluginContainerApplicationType.class))
  149. {
  150. if (getPluginKey().equals(otherLink.getProperty(PLUGIN_KEY_PROPERTY)))
  151. {
  152. log.debug("Old application link for this plugin '{}' found with different display url '{}', removing",
  153. getPluginKey(), displayUrl);
  154. applicationLinkService.deleteApplicationLink(otherLink);
  155. }
  156. }
  157. log.info("Creating an application link for the remote plugin container of key '{}'", getPluginKey());
  158. link = applicationLinkService.addApplicationLink(expectedApplicationId, applicationType, applicationLinkDetails);
  159. link.putProperty(PLUGIN_KEY_PROPERTY, getPluginKey());
  160. }
  161. link.putProperty("IS_ACTIVITY_ITEM_PROVIDER", Boolean.FALSE.toString());
  162. link.putProperty("system", Boolean.TRUE.toString());
  163. ServiceProvider serviceProvider = createOAuthServiceProvider(displayUrl, oauthElement);
  164. oAuthLinkManager.associateProviderWithLink(link, applicationType.getId().get(), serviceProvider);
  165. if (oauthElement != null)
  166. {
  167. registerOAuth(link, oauthElement);
  168. }
  169. }
  170. else
  171. {
  172. log.info("Plugin '{}' in local mode, so not setting up remote plugin container link", getPluginKey());
  173. }
  174. super.enabled();
  175. }
  176. private void manuallyDeleteApplicationId(ApplicationId expectedApplicationId)
  177. {
  178. PluginSettings pluginSettings = pluginSettingsFactory.createGlobalSettings();
  179. List<String> applicationIds = (List<String>) pluginSettings.get("applinks.global.application.ids");
  180. if (applicationIds != null)
  181. {
  182. if (applicationIds.remove(expectedApplicationId.get()))
  183. {
  184. pluginSettings.put("applinks.global.application.ids", applicationIds);
  185. }
  186. else
  187. {
  188. throw new IllegalStateException("Cannot find application id " + expectedApplicationId.get() + " to delete");
  189. }
  190. }
  191. else
  192. {
  193. throw new IllegalStateException("Cannot find application ids to manually delete " + expectedApplicationId.get());
  194. }
  195. }
  196. @Override
  197. public void disabled()
  198. {
  199. super.disabled();
  200. if (remoteMode && pluginBundle != null)
  201. {
  202. // we have to retrive services fresh from plugin bundle as it is possible this is called after the
  203. // remotable plugins plugin has been disabled
  204. MutatingApplicationLinkService applicationLinkService = getService(MutatingApplicationLinkService.class);
  205. for (ApplicationLink link : applicationLinkService.getApplicationLinks())
  206. {
  207. if (displayUrl.equals(link.getRpcUrl()))
  208. {
  209. log.info("Removing application link for display url '{}'", displayUrl);
  210. applicationLinkService.deleteApplicationLink(link);
  211. }
  212. }
  213. // this repeats logic in {@link OAuthLinkManager#unassociateConsumer()} because, again, all services
  214. // need to be retrieved fresh
  215. ServiceProviderConsumerStore store = getService(ServiceProviderConsumerStore.class);
  216. if (store.get(getPluginKey()) != null)
  217. {
  218. store.remove(getPluginKey());
  219. }
  220. }
  221. }
  222. private ServiceProvider createOAuthServiceProvider(URI displayUrl, Element oauthElement)
  223. {
  224. if (oauthElement != null)
  225. {
  226. final URI requestTokenUrl = URI.create(displayUrl + getOptionalAttribute(oauthElement, "request-token-url", "/request-token"));
  227. final URI accessTokenUrl = URI.create(displayUrl + getOptionalAttribute(oauthElement, "access-token-url", "/access-token"));
  228. final URI authorizeUrl = URI.create(displayUrl + getOptionalAttribute(oauthElement, "authorize-url", "/authorize"));
  229. return new ServiceProvider(requestTokenUrl, accessTokenUrl, authorizeUrl);
  230. }
  231. else
  232. {
  233. // set up the link with a dummy so that outgoing links get signed even if no oauth element
  234. // is defined
  235. URI dummyUri = URI.create("http://localhost");
  236. return new ServiceProvider(dummyUri, dummyUri, dummyUri);
  237. }
  238. }
  239. private void registerOAuth(ApplicationLink link, Element oauthElement)
  240. {
  241. permissionManager.requirePermission(getPluginKey(), Permissions.CREATE_OAUTH_LINK);
  242. final PluginInformation pluginInfo = getPlugin().getPluginInformation();
  243. final String name = getPlugin().getName();
  244. final String description = pluginInfo.getDescription();
  245. final URI callback = URI.create(displayUrl + getOptionalAttribute(oauthElement, "callback", "/callback"));
  246. final PublicKey publicKey = getPublicKey(getRequiredElementText(oauthElement, "public-key"));
  247. Consumer consumer = Consumer.key(getPluginKey()).name(name != null ? name : getPluginKey()).publicKey(publicKey).description(description).callback(
  248. callback).build();
  249. oAuthLinkManager.associateConsumerWithLink(link, consumer);
  250. // provider is already configured as part of the applink creation
  251. }
  252. protected final PublicKey getPublicKey(String publicKeyText)
  253. {
  254. PublicKey publicKey;
  255. try
  256. {
  257. if (publicKeyText.startsWith("-----BEGIN CERTIFICATE-----"))
  258. {
  259. publicKey = RSAKeys.fromEncodedCertificateToPublicKey(publicKeyText);
  260. }
  261. else
  262. {
  263. publicKey = RSAKeys.fromPemEncodingToPublicKey(publicKeyText);
  264. }
  265. }
  266. catch (GeneralSecurityException e)
  267. {
  268. throw new PluginParseException("Invalid public key", e);
  269. }
  270. return publicKey;
  271. }
  272. private <T> T getService(Class<T> interfaceClass)
  273. {
  274. if (pluginBundle != null)
  275. {
  276. return OsgiServiceUtils.getService(pluginBundle.getBundleContext(), interfaceClass);
  277. }
  278. else
  279. {
  280. throw new IllegalStateException("Cannot retrieve services from unknown plugin bundle: " + getCompleteKey());
  281. }
  282. }
  283. @Override
  284. public Void getModule()
  285. {
  286. return null;
  287. }
  288. }