Code spelunking to build a CF Plugin

This is a quick "how to" for how Long found the information he needed to build the cf info plugin.

Following a trail of breadcrumbs

Determine the requirements:

  1. Print the currently targeted org and space
  2. Print the API version and endpoint
  3. Print the user id of the current user (similar to whoami in *nix OSes).

Fulfilling the requirements:

This information comes from cf target, so first we’ll take a look in target.go.

  • Line 83 in target.go prints the current session information as reported from ui.go.
  • Line 196 in ui.go references a UserEmail method.
  • Line 175 of ui.go shows that config is actually from the core_config package.
  • There are a few *.go files in the core_config package, but searching this repository for UserEmail shows that the method is defined in config_repository.go on lines 199-204.
  • UserEmail requires a struct, called ConfigRepository.
  • ConfigRepository is built from the NewRepositoryFromPersistor method. (You can tell this both by the return &ConfigRepository on line 23 or by noting that NewRepositoryFromPersistor returns all the fields needed by the struct – i.e. data, mutex, initOnce, persistor, and onError.)
  • NewRepositoryFromPersistor is returned from the method above it, NewRepositoryFromFilepath.

How to get the file path? Searching for CF_HOME (environmental variable storing the home directory) shows that it is defined in config_helpers.go. Now what to do with that information:

  • The DefaultFilePath is defined using either $CF_HOME/.cf or $HOME/.cf ($HOME is the user’s home directory in Unix environments).
  • As a sanity check, if you are using a Unix environment run echo $CF_HOME. If it is non-empty, then run less $CF_HOME/.cf/config.json. If it is empty, run less $HOME/.cf/config.json. You should see the configuration file that cf target uses to report the org, space, and API information (there is other information in there as well).
  • Recall that UserEmail is defined in lines 199-204 of config_repository.go. You can see that config_repository.go also has the other information we wish to pull – the org (OrganizationFields), space (SpaceFields), API endpoint (ApiEndpoint), and API version (ApiVersion).

What to do with all of that

Basically everything in reverse. You supply the default file path, which is config.json, to NewRepositoryFromFilepath. config.json has the org, space, API endpoint, API version, and Access Token. The existing cf code decodes the Access Token and the UserEmail method extracts the email address from that information. Everything is just pulled directly from the repository you just defined.

Addendum: What is going on to get that email anyway?

Let’s take a quick look in config.json. The 7th line should be the Access Token, which should look similar to this:

"AccessToken": "bearer eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI3NzI2ZDE2MS1hNWUzLTQwZjMtYTEzYy00OTlmMDNjOTBhZGIiLCJzdWIiOiI2Y2I5MTA0Yy1hZTMxLTQxYTMtOGQ4MS1jYjUxZjg0MTk5ZTMiLCJzY29wZSI6WyJjbG91ZF9jb250cm9sbGVyLmFkbWluIiwiY2xvdWRfY29udHJvbGxlci5yZWFkIiwiY2xvdWRfY29udHJvbGxlci53cml0ZSIsIm9wZW5pZCIsInBhc3N3b3JkLndyaXRlIiwic2NpbS5yZWFkIiwic2NpbS53cml0ZSJdLCJjbGllbnRfaWQiOiJjZiIsImNpZCI6ImNmIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9pZCI6IjZjYjkxMDRjLWFlMzEtNDFhMy04ZDgxLWNiNTFmODQxOTllMyIsInVzZXJfbmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbiIsImlhdCI6MTQxNzgxMTU3NywiZXhwIjoxNDE3ODEyMTc3LCJpc3MiOiJodHRwczovL3VhYS41NC4xNzQuMjI5LjExOS54aXAuaW8vb2F1dGgvdG9rZW4iLCJhdWQiOlsic2NpbSIsIm9wZW5pZCIsImNsb3VkX2NvbnRyb2xsZXIiLCJwYXNzd29yZCJdfQ.XNaYq8rxpvwWx9kySIDqbKs0BuyeOMMwAPb5YQaT-9MIyr3YalCE_2gTg-fl0xulj4u-VoNme3OGZ2T3tFFUfBKgo3U7R_pl5OpcaetKslbvKtYpne7N30KMQySMqVVVooGqlReoI_n5m5O7ZIASiG8P1QtwuVrZPkPhbjsGfBE",

Source: The above is the access token from a now-destroyed TryCF instance.

This is a JSON Web Token (JWT). You can read about JWT here. For now, all I really care about is that its structure is <header>.<claims>.<signature>. The <claims> section of the above token is between the two periods:

eyJqdGkiOiI3NzI2ZDE2MS1hNWUzLTQwZjMtYTEzYy00OTlmMDNjOTBhZGIiLCJzdWIiOiI2Y2I5MTA0Yy1hZTMxLTQxYTMtOGQ4MS1jYjUxZjg0MTk5ZTMiLCJzY29wZSI6WyJjbG91ZF9jb250cm9sbGVyLmFkbWluIiwiY2xvdWRfY29udHJvbGxlci5yZWFkIiwiY2xvdWRfY29udHJvbGxlci53cml0ZSIsIm9wZW5pZCIsInBhc3N3b3JkLndyaXRlIiwic2NpbS5yZWFkIiwic2NpbS53cml0ZSJdLCJjbGllbnRfaWQiOiJjZiIsImNpZCI6ImNmIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9pZCI6IjZjYjkxMDRjLWFlMzEtNDFhMy04ZDgxLWNiNTFmODQxOTllMyIsInVzZXJfbmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbiIsImlhdCI6MTQxNzgxMTU3NywiZXhwIjoxNDE3ODEyMTc3LCJpc3MiOiJodHRwczovL3VhYS41NC4xNzQuMjI5LjExOS54aXAuaW8vb2F1dGgvdG9rZW4iLCJhdWQiOlsic2NpbSIsIm9wZW5pZCIsImNsb3VkX2NvbnRyb2xsZXIiLCJwYXNzd29yZCJdfQ

If you paste that information into Base 64 Decode, you can see the user’s credentials – including the email field. (For TryCF the user_name and email are both admin.) If you paste in the above and decode it, you will see:

{"jti":"7726d161-a5e3-40f3-a13c-499f03c90adb","sub":"6cb9104c-ae31-41a3-8d81-cb51f84199e3","scope":["cloud_controller.admin","cloud_controller.read","cloud_controller.write","openid","password.write","scim.read","scim.write"],"client_id":"cf","cid":"cf","grant_type":"password","user_id":"6cb9104c-ae31-41a3-8d81-cb51f84199e3","user_name":"admin","email":"admin","iat":1417811577,"exp":1417812177,"iss":"https://uaa.54.174.229.119.xip.io/oauth/token","aud":["scim","openid","cloud_controller","password"]}

Feel free to try it with your own Access Token! 🙂

Spread the word

twitter icon facebook icon linkedin icon