OrchardCMS / OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
https://orchardcore.net
BSD 3-Clause "New" or "Revised" License
7.39k stars 2.39k forks source link

Orchard Core as a dotnet cli tool #2636

Open sebastienros opened 5 years ago

sebastienros commented 5 years ago

We should have a dotnet global tool as the cli. This tool should work using REST APIs only. This means the Orchard Core application would need to be started already, which is normal and already supported by the dotnet sdk (we can build and start the application using the sdk, so anything would be done from the command line and from scripts).

The only issue is how would the CLI be extended by specific modules. After all some features will only be available with some features. And also authorization: should it be able to work remotely, how do we ensure the client has access to the instance (private key?).

Here is the Drupla CLI for reference: https://github.com/drush-ops/drush

jrestall commented 5 years ago

I like the simplicity of personal access tokens, but I also wonder how simple the OpenId flow could be or if it could be used behind the personal access tokens. @PinpointTownes

https://www.contentful.com/r/knowledgebase/personal-access-tokens/ https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/

sebastienros commented 5 years ago

Funny you suggest it, I actually talked with @PinpointTownes about simpler ways to authenticate in client apps, and he suggested PATs. And also that it should have it's custom module. So I assume we could have a page to create, list, revoke any PAT. And an authentication handler that would look for these in a header and impersonate the user.

kevinchalet commented 5 years ago

Funny you suggest it, I actually talked with @PinpointTownes about simpler ways to authenticate in client apps, and he suggested PATs.

Well, I mentioned PATs (because it was the option corresponding to your criteria when we chatted), but I didn't say I suggested using them :sweat_smile:

Personally, I'd likely use the authorization code flow, by launching the default browser and running a temporary HTTP server to catch the callback authorization response or registering a custom system URI scheme (probably less handy for a CLI tooling).

The device code flow might also be a good option. If you want to see it in action, give the Azure CLI tooling a try. The downside is that it's still not officially standardized (and thus not supported by OpenIddict).

jrestall commented 5 years ago

The Contentful CLI tooling uses the authorization code flow that opens a browser window and has you authenticate the app. I'm a little biased towards personal access tokens though since I need them to configure with Netlify to call back into Orchard when rebuilding my site.

https://github.com/contentful/contentful-cli

The only issue is how would the CLI be extended by specific modules. After all some features will only be available with some features.

So here's a crazy idea. Let's go all in on GraphQL. Since we can then do an introspection on the schema and dynamically build the list of supported CLI commands from the mutations that the modules provide.

So this is not something that has been done before and I'm only familiar with how we might do it as an npm package. Is dotnet global tool a must?

Here's some code that dynamically introspects a given schema from a remote URL and makes it available to call easily via the Delegate class. We would need to add in the schema to yargs code to be able to run graphql mutations as commands, but I think it should be doable.

(Ignore that it's an express app - was quickest way for me to test that the code worked)

const fetch = require('node-fetch')
const express = require('express');
const { Delegate } = require('graphql-binding/dist/Delegate')
const { HttpLink } = require('apollo-link-http')
const { setContext } = require('apollo-link-context');
const { introspectSchema, makeRemoteExecutableSchema, WrapQuery } = require('graphql-tools')

async function getExecutableSchema(link) {
  const schema = await introspectSchema(link);

  const executableSchema = makeRemoteExecutableSchema({
    schema,
    link,
  });

  return executableSchema
}

// Create the remote schema
const http = new HttpLink({ uri: 'http://orchardcoredomain/graphql', fetch });

const link = setContext((request, previousContext) => ({
  headers: {
    'Authorization': `Bearer 12345`,
  }
})).concat(http);

(async () => {
  const schema = await getExecutableSchema(link);

  // Create the `before` function
  const before = () => console.log(`Sending a request to the GraphQL API ...`)

  const delegate = new Delegate({ schema, before })

  const app = express();

  app.get('/', (req, res) => {

  // Testing Delegate
  const args = { name: `My Tenant`, recipeName: `Blog` }

  delegate.delegate(
    `mutation`,
    `createTenant`,
    args
  )
    .then(createTenantResult => res.send(JSON.stringify(createTenantResult)).sendStatus(200))
    .catch((e) => { console.error(e) })
  })

  const listener = app.listen(process.env.PORT, function() {
    console.log('Your app is listening on port ' + listener.address().port);
  });
})();
sebastienros commented 5 years ago

I totally agree with all the things you said.

kevinchalet commented 5 years ago

The Contentful CLI tooling uses the authorization code flow that opens a browser window and has you authenticate the app.

Good to know! I'm not really surprised as it's certainly the most convenient approach (tho' it requires having a machine with a GUI, it's easier to use than the device code flow as you don't even have to copy/paste some user codes).

If we go with this approach, we can make it as simple as clicking on a "enable remote management" button in the admin UI (which would create an OpenID Connect client registration and some Orchard-specific scopes for the CLI tooling).

sebastienros commented 5 years ago

How does it work when we use a CLI like in Docker, could we use a different flow, and type the creds in the console app instead?

kevinchalet commented 5 years ago

The password flow could be used for that, but if we want the "state of the art" option for headless machines (e.g a CLI executed via SSH or in a Docker container), we'll want to use the device code flow once it's standardized. It's what the Azure CLI tooling uses.

kevinchalet commented 5 years ago

image

sebastienros commented 5 years ago

Interesting. Then how long is the authentication valid once the code has been registered on the web app? What does it store locally?

kevinchalet commented 5 years ago

Then how long is the authentication valid once the code has been registered on the web app?

It's completely implementation-specific and depends on the lifespan of the access token returned by the authorization server. If a refresh token is returned, the CLI can retrieve new access tokens so nothing prevents us from having a never expiring authentication logic.

What does it store locally?

It can store the access token/refresh token or keep everything in-memory, depending on your choices.

jrestall commented 5 years ago

I've started creating a node based CLI since the library support makes it a lot easier.

It's here currently but we can move it if you think it should be part of Orchard Core. https://github.com/jrestall/orchardcore-cli

It will generate a CLI based on the graphql endpoint you give it, so when pointing to http://api.githunt.com/graphql it will generate the following options:

orchardcore -h

Welcome to Orchard Core CLI

Usage: orchardcore <cmd> [args]
Commands:
  orchardcore config                     Sets the CLI config
  orchardcore submitRepository           Submit a new repository, returns the
  <repoFullName>                            new submission
  orchardcore vote <repoFullName>        Vote on a repository submission,
  <type>                                    returns the submission that was
                                            voted on
  orchardcore submitComment              Comment on a repository, returns the
  <repoFullName> <commentContent>           new comment
Options:
  -h, --help     Show help                                             [boolean]
  -v, --version  Show version number                                   [boolean]

For more information, find the documentation at https://orchardcore.readthedocs.io/

It can show detailed documentation on specific mutations also:

orchardcore vote -h

Welcome to Orchard Core CLI

orchardcore.js vote <repoFullName> <type>

Vote on a repository submission, returns the submission that was voted on
Positionals:
  repoFullName  The full repository name from GitHub, e.g.
                "apollostack/GitHunt-API"                    [string] [required]
  type          The type of vote - UP, DOWN, or CANCEL
                           [string] [required] [choices: "UP", "DOWN", "CANCEL"]
Options:
  -h, --help     Show help                                             [boolean]
  -v, --version  Show version number                                   [boolean]
jrestall commented 5 years ago

I've added dotnet global tool support to the orchard-cli by using NodeServices. So now we support both nodejs and dotnet.

https://github.com/jrestall/orchardcore-cli/blob/master/Program.cs

To install & test:

dotnet pack
dotnet tool install --global --add-source ./nupkg orchardcore-cli

orchardcore config --host "https://api.githunt.com/graphql"
orchardcore -help
orchardcore vote -help
orchardcore vote myRepoName UP
rserj commented 5 years ago

It looks like a good idea, as I understand Orchard instance must have GraphQL and OpenId modules enabled before using Orchard CLI. Probably we can prepare some recipe which will provide "Orchard core CLI support" by enabling all necessary modules and probably adding some default ClientIds, Client Secrets for b2b and b2c scenarios.

Piedone commented 4 years ago

Related: https://github.com/OrchardCMS/OrchardCore/issues/1742

kevinchalet commented 4 years ago

For the record, OpenIddict 3.0 supports the device flow (which was standardized last year), so we'll be able to update the OpenID module to enable this flow.

sebastienros commented 4 years ago

@kevinchalet that's great, I love this flow as a user. Do you know any CLI that already uses it? I have never seen one so far. Only actual devices.

kevinchalet commented 4 years ago

The Azure Powershell CLI supports it, tho' it's no longer the default flow (by default, it uses the code flow and starts the authorization dance in a webview, which is meh...). To see the device flow in action:

Connect-AzAccount -UseDeviceAuthentication
sebastienros commented 4 years ago

Related cli https://blog.repl.it/clui

It takes the graphql schema to build the UI/cli