186

Rider NuGet Credential Provider for JetBrains Space private repositories

 3 years ago
source link: https://blog.jetbrains.com/dotnet/2021/05/20/rider-nuget-credential-provider-for-jetbrains-space-private-repositories/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
.NET Tools How-To's

Rider NuGet Credential Provider for JetBrains Space private repositories

Avatar
Maarten Balliauw May 20, 2021

With the 2021.1 release of our IDE’s, we also made available the Space plugin. This plugin lets you authenticate with a Space organization, work with automation scripts for CI/CD, and browse Space code reviews.

In Space, you can also create private NuGet feeds that your team can use. So, the .NET developer advocacy team was thinking… 🤔

What if we create a Rider plugin, that automatically logs you in to your private Space NuGet repositories?

Welcome, Rider NuGet Credential Provider for Space!

Intermezzo: What is Space?

Not everyone may have already heard about Space, so let’s try to summarize what it is in one sentence…

JetBrains Space brings software developers together with chats, project management, issue tracking, Git hosting, CI/CD, and package repositories.

Create a free Space organization

There is much more functionality built in, such as chats, a team directory, and more. Go check it out if you’re curious about what Space has to offer.

End intermezzo.

Let’s look at what our plugin does, and then look at how it was developed.

Working with NuGet packages from Space in Rider

When you have the Space plugin installed and configured in your IDE, the Rider NuGet Credential Provider for Space automatically authenticates with private NuGet feeds hosted in Space packages.

First, authenticate with Space from within your IDE. Check the Space plugin documentation for more information.

Next, configure a Space NuGet feed in your NuGet.config file. You can add a NuGet.config next to your solution file,
and configure it similar to the following:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="SpaceNuGet" value="https://nuget.pkg.jetbrains.space/mycompany/p/projectkey/mynuget/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <add key="SpaceNuGet" value="https://nuget.pkg.jetbrains.space/mycompany/p/projectkey/mynuget/v3/index.json" protocolVersion="3" />
    </packageSources>
</configuration>

A picture says more than a thousand words, so here’s what our plugin does:

Space Packages - Private NuGet feed in Rider

Note: Make sure you have Rider credential providers enabled. In Settings | Build, Execution, Deployment | NuGet, select Rider integrated or NuGet/.NET CLI plugins, then Rider integrated.

Background about how this plugin works

In Rider 2018.2, we introduced credential providers for private feeds in our NuGet client. While it has evolved, and now also supports dotnet and NuGet.exe credential providers, the extension point for NuGet credential providers is still there. Great!

Building a plugin for Rider

Using the Rider plugin template, we got to work and created a barebones plugin that we could run in Rider.

We stripped off the ReSharper bits, as the NuGet credential provider extension point is Rider-only.

Adding plugin dependencies and registering an extension

Next, we updated the build.gradle file, and made some changes to the intellij section. Our plugin would need access to the Space plugin, so we need a dependency on it:

// ...
intellij {
type = 'RD'
version = '2021.1.0'
downloadSources = false
instrumentCode = false
plugins("com.jetbrains.space:211.6693.108")
// ...
// ...

intellij {
    type = 'RD'
    version = '2021.1.0'
    downloadSources = false
    instrumentCode = false
    plugins("com.jetbrains.space:211.6693.108")
}

// ...

Using this approach, we can make use of the classes and services provided by the Space plugin to get information about the currently logged in user.

Note: Rider is built on the IntelliJ platform and ReSharper, as described in more detail in this article in CODE Magazine. Our plugin lives on the IntelliJ side, which means we need to write it in a JVM language like Kotlin, and make use of build tools like Gradle.

In the plugin.xml manifest, which describes our plugin and what it provides, we added a plugin dependency. Doing so ensures that if you download our plugin in Rider, the IDE will also download the Space plugin. A <depends>com.jetbrains.space</depends> did the trick.

The credential providers post describes how to add a custom credential provider, which is what we did:

<extensions defaultExtensionNs="com.intellij">
<nugetCredentialProvider
implementation="org.jetbrains.rider.nuget.credentials.providers.space.SpaceNuGetCredentialProvider"
order="first" />
</extensions>
<extensions defaultExtensionNs="com.intellij">
    <nugetCredentialProvider
            implementation="org.jetbrains.rider.nuget.credentials.providers.space.SpaceNuGetCredentialProvider"
            order="first" />
</extensions>

One thing left: building the actual credential provider!

Implementing NuGetCredentialProvider

Our custom NuGetCredentialProvider, aptly named SpaceNuGetCredentialProvider, is used by Rider when credentials are required to access a NuGet feed.

It has to:

  • Let Rider know if it can handle a specific NuGet URL (in the appliesTo(project: Project, uri: URI): Boolean function); and
  • Provide access details if possible (in the getProviderResponse(project: Project, uri: URI, isRetry: Boolean): NuGetCredentialProviderResponse function).

A skeleton NuGet credential provider in Rider looks like this:

class SpaceNuGetCredentialProvider : NuGetCredentialProvider {
override fun appliesTo(project: Project, uri: URI): Boolean { }
override fun getProviderResponse(project: Project, uri: URI, isRetry: Boolean): NuGetCredentialProviderResponse { }
class SpaceNuGetCredentialProvider : NuGetCredentialProvider {
    override fun appliesTo(project: Project, uri: URI): Boolean { }
    override fun getProviderResponse(project: Project, uri: URI, isRetry: Boolean): NuGetCredentialProviderResponse { }
}

In the appliesTo function, we implemented a check to see if the IDE is logged in, and whether the URL of the feed matches the Space organization we’re logged in to:

override fun appliesTo(project: Project, uri: URI): Boolean {
if (!settings.serverSettings.enabled) return false
// uri and server are Space cloud, and are the same organization
if (uri.host.equals("nuget.pkg.jetbrains.space", ignoreCase = true)) {
val organizationName = uri.path.trimStart('/').split('/').firstOrNull()
?: return false
if (settings.serverSettings.server.startsWith("https://$organizationName.jetbrains.space", ignoreCase = true)) {
return true
return false
override fun appliesTo(project: Project, uri: URI): Boolean {

    if (!settings.serverSettings.enabled) return false

    // uri and server are Space cloud, and are the same organization
    if (uri.host.equals("nuget.pkg.jetbrains.space", ignoreCase = true)) {
        val organizationName = uri.path.trimStart('/').split('/').firstOrNull()
            ?: return false

        if (settings.serverSettings.server.startsWith("https://$organizationName.jetbrains.space", ignoreCase = true)) {
            return true
        }
    }

    return false
}

Not 100% bullet proof, but it will do for this first version of our plugin.

Next, the getProviderResponse function. It has to return a NuGetCredentialProviderResponse, which will tell Rider what the authentication status is. First, we check whether we’re still logged in. If not, we can return NuGetCredentialProviderResponse.PROVIDER_NOT_APPLICABLE.

The second step is to check whether the user has manually configured credentials (in the Sources tab of the NuGet tool window). Credential providers don’t have to do this, but it’s nice if they give the user a means of overriding credentials.

override fun getProviderResponse(project: Project, uri: URI, isRetry: Boolean): NuGetCredentialProviderResponse {
if (!settings.serverSettings.enabled) return NuGetCredentialProviderResponse.PROVIDER_NOT_APPLICABLE
// Use already stored credentials, if available.
// The user may have configured these manually in Rider UI, and we want to try those first.
if (!isRetry) {
val storedCredentials = RiderNuGetCredentialsRepo.loadCredentialsForFeed(uri.toString())
if (storedCredentials != null) {
return NuGetCredentialProviderResponse(
NuGetCredentials(storedCredentials.user, storedCredentials.password),
NuGetCredentialProviderStatus.Success,
null)
// ...
override fun getProviderResponse(project: Project, uri: URI, isRetry: Boolean): NuGetCredentialProviderResponse {

    if (!settings.serverSettings.enabled) return NuGetCredentialProviderResponse.PROVIDER_NOT_APPLICABLE

    // Use already stored credentials, if available.
    // The user may have configured these manually in Rider UI, and we want to try those first.
    if (!isRetry) {
        val storedCredentials = RiderNuGetCredentialsRepo.loadCredentialsForFeed(uri.toString())
        if (storedCredentials != null) {
            return NuGetCredentialProviderResponse(
                NuGetCredentials(storedCredentials.user, storedCredentials.password),
                NuGetCredentialProviderStatus.Success,
                null)
        }
    }

    // ...
}

Only after these two checks, we call into the Space plugin and retrieve an authentication token for the current user.

// ...
val workspaceContext = SpaceNuGetWorkspace.getWorkspaceContext()
?: return NuGetCredentialProviderResponse.PROVIDER_NOT_APPLICABLE
val username = workspaceContext.profile.username
val accessToken = workspaceContext.circletClient.tokenSource.token().accessToken
return NuGetCredentialProviderResponse(
NuGetCredentials(username, RdSecureString(accessToken)),
NuGetCredentialProviderStatus.Success,
null)
// ...

val workspaceContext = SpaceNuGetWorkspace.getWorkspaceContext()
    ?: return NuGetCredentialProviderResponse.PROVIDER_NOT_APPLICABLE

val username = workspaceContext.profile.username
val accessToken = workspaceContext.circletClient.tokenSource.token().accessToken

return NuGetCredentialProviderResponse(
    NuGetCredentials(username, RdSecureString(accessToken)),
    NuGetCredentialProviderStatus.Success,
    null)

That’s it! Now, you may wonder… Where is the source code for this plugin? Currently, it resides in a private (Space) repository, but we plan on publishing it in the future.

Conclusion

In this blog post, we wanted to provide some resources related to building Rider plugins. We walked through how we built our own plugin for Rider, to help with private NuGet feeds hosted in Space.

Give JetBrains Space a try, and make sure to use our Rider NuGet Credential Provider for Space if you do.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK