Skip to content

Instantly share code, notes, and snippets.

@tm-schwartz
Last active January 1, 2022 21:58
Show Gist options
  • Save tm-schwartz/8db89504d37377d2ca24dc992c12b42f to your computer and use it in GitHub Desktop.
Save tm-schwartz/8db89504d37377d2ca24dc992c12b42f to your computer and use it in GitHub Desktop.
Brightspace API interaction with Julia

Refresh Token Helper Functions

Imports

import JSON
import URIs
import HTTP

OAUTH Endpoints

const token_endpoint = "https://auth.brightspace.com/core/connect/token"
const auth_endpoint = "https://auth.brightspace.com/oauth2/auth"

Function: genparams

function genparams(tokenpath, configpath)
    tokens = JSON.parsefile(tokenpath)
    conf = JSON.parsefile(configpath)
    refreshparams = Dict([("scope" => j["scope"]),
                   ("refresh_token" => j["refresh_token"]), 
                   ("grant_type" => "refresh_token"), 
                   ("client_id" => conf["client_id"]), 
                   ("client_secret" => conf["client_secret"])])
    return (tokens=>tokens, conf=>conf, refreshparams=>refreshparams)
end

Inputs

tokenpath

  • path to json file in following format:
{"access_token":<access token>,
"token_type":"Bearer",
"scope":<scope>,
"expires_in":<expired in>,
"refresh_token":<refresh token>}

configpath

  • path to json file in following format:
{"client_id": <client id>,
"client_secret": <client secret>,
"redirect_uri": <redirect uri>}

Tokenpath and configpath are parsed into a parameter dictionary ready for use in a refresh request, and returned as an element of a named tuple along with the tokens dictionary and configuration dictionary.

Function: genbod

function genbod(params)
    bod = string(URIs.URI(query = params))
    return bod[2:length(bod)]
end

Inputs

params

  • the refreshparams dictionary/element returned from genparams, or a dictionary of form:
Dict([("scope" => <scope>),
      ("refresh_token" => <refresh token>), 
      ("grant_type" => "refresh_token"), 
      ("client_id" => <client id>), 
      ("client_secret" => <client secret>)])

This function uses URIs to generate a query, then removes the leading '?' and returns a string. This is necessary because the brightspace API expects a string of this form in the body argument of HTTP.request.

Function: refresh

function refresh(body, newtok)
    req = HTTP.request("POST",token_endpoint, ["Content-Type"=>"application/x-www-form-urlencoded"], body)
    tok = String(req.body)
    open(newtok, "w") do f
       write(f, tok)
     end
 end

Inputs

body

  • query string for submission in body parameter of HTTP.request.

newtok

  • file name to save new access/refresh tokens, scope, expiration etc.

Submits a POST request to token endpoint, then writes new tokens to file.

Refresh Token

staleparams = genparams("token.json", "config.json")
refresh(genbod(staleparams.refreshparams), "new_token.json")

We now have a live access token, we can read the new data by calling genparams again.

freshparams  = genparams("new_token.json", "config.json")

Example Workflow

Get Tokens

To start, we need to get an access and refresh token. This is done by sending a GET request to the authentication endpoint.

tokpath = joinpath(path, "new_token.json")
confpath = joinpath(path,"d2lconfig.json")
staleparams = genparams(tokpath, confpath)
params = Dict([
("response_type"=>"code"), 
("client_id"=>staleparams.conf["client_id"]), 
("redirect_uri"=>staleparams.conf["redirect_uri"]),
("scope"=>staleparams.tokens["scope"]), 
("state"=>"jfjkds31l")
])

authcoderesp = HTTP.request("GET", auth_endpoint, []; query=params)

authcodeurl = joinpath(authcoderesp.request.headers[end][2], authcoderesp.request.target[2:end])

authcodeurl is a string of the form dev.brightspace.com/d2l/auth/api/token?<token>target=<target>. This string needs to be copied into a browser URL bar and followed. It will redirect to the redirect_url and the URL w/i the URL bar will contain a parameter code=<code>. This is the authorization code and should be

The next step is to request the tokens. We need a slightly different param dictionary than the one generated by genparams, so we can manually create a params dictionary for genbod.

params = Dict([
("grant_type"=>"authorization_code"),
("client_id"=>staleparams.conf["client_id"]),
("client_secret"=>staleparams.conf["client_secret"]),
("redirect_uri"=>staleparams.conf["redirect_uri"]),
("scope"=>staleparams.conf["scope"]),
("code"=><code>)
])

refresh(genbod(params), "new_token.json")
freshparams = genparams("new_token.json", "config.json")

We can now send an API request using the access token.

r = HTTP.request("GET", joinpath(urlbase, "d2l/api/lp/<vers>/dataExport/bds/list"), ["Authorization"=> "Bearer $(freshparams.tokens["access_token"])"],[] )

Where urlbase is the address of your brightspace dev instance and <vers> is your brightspace version. This request will return a list of data sets. Given a list of data sets datasetstoget, we can process the response.

First, we parse the json response and push the data sets we are interested in to an array.

res = JSON.parse(r.body)

dictlist = []
for dct in res
   if (dct["Name"] in datasetstoget)
   push!(dictlist, dct)
   end
end

Next, we loop over the array and download the datasets. Brighspace returns zip files, which can be written directly to disk.

for ds in dictlist
   r = HTTP.request("GET", joinpath(urlbase, "d2l/api/lp/<vers>/dataExport/bds/download/$(ds["PluginId"])"), ["Authorization"=> "Bearer $(freshparams.tokens["access_token"])"],[])
   
   open("$(replace(ds["Name"], " "=>"")).zip" ,"w") do f
   write(f, r.body)
   end
end

Finally, we can read the contents of the current directory and read the data into memory.

cdir = readdir()
ncdir = [x for x in cdir if x[length(x)-3:length(x)] == ".zip"]
cdat = []
for f in ncdir
   zr = ZipFile.Reader(f)
   push!(cdat, Symbol(split(zr.files[1].name, ".")[1])=>CSV.read(zr.files[1], DataFrame))
   close(zr)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment