A Few Words About OAC Embedding
How to embed OAC DV projects into third-party sites
TL;DR To exit VIM you pressEsc
, then type:q!
to just exit or:wq
to save changes and exit and then pressEnter
.
Some time ago and by that I mean almost exactly approximately about two years ago Mike Durran (https://insight2action.medium.com) wrote a few blogs describing how to embed Oracle Analytics Cloud (OAC) contents into public third-party sites.
For anyone who needs to embed OAC reports into their sites, these blogs are a must-read and a great source of valuable information. Just like his other blogs and the official documentation, of course.
If you have ever tried it, you most likely noticed that the embedding process is not exactly easy or intuitive. Roughly it consists of the following steps:
- Create content for embedding.
- Setup infrastructure for authentication:
2.1. Create an Oracle Identity Cloud Service (IDCS) application.
2.2.Create an Oracle Functions function.
2.3. Set up Oracle API Gateway. - Embed JavaScript code to the third-party site.
Failing to implement any of the above leads to a fully non-functional thing.
And here is the problem: Mike knows this well. Too well. Some things that are entirely obvious to him aren't obvious to anyone trying to implement it for the first time. When you know something at a high level, you tend to skip bits and bobs here and there and various tasks look easier than they are.
A small story
When I was studying at the university, our techer told us a story. Her husband was writing a math book for students and wrote the infamous phrase all students love: "... it is easy to prove that ...". She said to him that, if it was easy to prove, he should do it.He spent a week proving it.
That is why I think that I can write something useful on this topic. I'm not going to repeat everything Mike wrote, I'm not going to re-write his blog. I hope that I can fill in a few gaps and show some it is easy to do things.
Also, this blog is not intended to be a complete step-by-step guide. Or, at least, I have no intention of writing such a thing. Although, it frequently happens that I'm starting to write a simple one-paragraph hint and a few hours later I'm still proofreading something with three levels of headers and animated screen captures.
Disclaimer. This blog is not a critique of Mike's blog. What he did is hard to overestimate and my intention is just to fill some gaps.
Not that I needed to make the previous paragraph a disclaimer, but all my blogs have at least one disclaimer and once you get locked into a serious disclaimers collection, the tendency is to push it as far as you can.
Testing out Token Generation
My main problem with this section is the following. Or, more precisely, not a problem but a place that might require more clarification in my opinion.
You’ll see that the token expires in 100 seconds and I describe how to increase that in a separate blog. For now, you can test this token will authenticate your OAC embedded content by copying the actual token into the following example HTML and deploying on your test web server or localhost (don’t forget to add suitable entries into the OAC safe domains page in the OAC console)
I mean why exactly 100 seconds is a bad value? What problem does increasing this value solve? Or, from the practical point of view, how do we understand that our problem is the token lifespan?
It is easy and confusing at the same time. The easy part is that after the token is expired, no interaction with the OAC is possible. It is not a problem if you embed non-interactive content. If the users can only watch but do not touch, the default value is fine. However, if the users can set filters or anyhow interact with reports, tokens must live longer than the expected interaction time.
Here is what it looks like when the token is OK:
And the same page a few minutes later:
Assuming that we don't know the right answer and need to find it, how do we do it? The browser developer console is your friend! The worst thing you can do to solve problems is to randomly change parameters and press buttons hoping that it will help (well, sometimes it does help, but don't quote me on that). To actually fix it we need to understand what is going on.
To be fair, at first sight, the most obvious and visible message is totally misleading. Normally, we go to the Console tab (Ctrl+Shift+J/Command+Option+J) and read what is written there. But if the token is expired, we get this:
The console shows multiple CORS errors: Access to XMLHttpRequest at 'https://OAC-INSTANCE-NAME.analytics.ocp.oraclecloud.com/ui/dv/ui/api/v1/sessioninfo/ext' from origin 'https://THIRD-PARTY-SITE' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
CORS stands for Cross-Origin Resource Sharing. In short, CORS is a security mechanism implemented in all modern browsers which allows for specifying if content from one server may be embedded into another server.
So looking at this we might assume that the solution would be either specify Safe domains in OAC or set CORS policy for our Web server, or both. In reality, this message is misleading. The real error we can get from the Network tab.
Let's take a look at the first failed request.
Simply click it once and check the Headers tab. Here we can clearly see that the problem is caused by the token, not by CORS. The token is expired.
The same approach shows when there is something wrong with the token. For example, once I selected a wrong OAC instance for the Secure app. Everything was there. All options were set. All privileges were obtained. The token generation was perfect. Except it didn't work. The console was telling me that the problem was CORS. But here I got the real answer.
Oracle Functions Service
I feel like this is the part which can use more love. There are a few easy-to-miss things here.
And the most important thing is why do we need Oracle Functions at all? Can't we achieve our goal without Functions? And the answer is yes, we can. Both Oracle Functions and API Gateways are optional components.
In theory, we can use the Secure application directly. For example, we can set up a cron job that will get the token from the Secure application and then embed the token directly into static HTML pages using sed
or Python or whatever we like. It (theoretically) will work. Note, that I didn't say it was a better idea. Or even a good one. What I'm trying to say is that Functions is not an essential part of this process. We use Oracle Functions to make the process more manageable, but it is only one of the possible good solutions, not the only one.
So what happens at this step is that we are creating a small self-containing environment with a Node.js application running in it. It all is based on Docker and Fn Project, but it is not important to us.
The function we are creating is a part required to simplify the result.
High-level steps are:
- Create an application.
- Open the application and either use Cloud Shell (the easy option) or set up a development machine.
- Init a boilerplate code for the function.
- Edit the boilerplate code and write your own function.
- Deploy the code.
- Run the deployed code.
Creating a new function is easy. Go to Developer Services -> Applications
Create a new function and set networking for it. The main thing to keep in mind here is that the network should have access to Oracle Cloud Infrastructure Registry. If it doesn't have access, you'll get Fn: Error invoking function. status: 502 message: Failed to pull function image
error message when trying to run the function: Issues invoking functions.
The first steps with Oracle functions are simple and hard at the same time. It is simple because when you go to Functions, you see commands which should be executed to get it up and running. It is hard because it is not obvious what is happening and why. And, also, diagnostics could've been better if you ask me.
After you create an application, open it, go to the Getting started, press Launch Cloud Shell and do what all programmers do: copy and paste commands trying to look smart and busy in the process. Literally. There are commands you can copy and paste and get a fully working Hello World example written in Java. Just one command has a placeholder to be changed.
Hint: to make your life easier first do step #5 (Generate an Auth Token
) and then come back to the steps 1-4 and 6-11.
If everything is fine, you will see "Hello, world!" message. I wonder, does it make me a Java developer? At least a junior? I heard that is how this works.
OK, after the Java hello-world example works, we can add Node.js to the list of our skills. Leave the Java hello-world example and initialize a new node
function:
cd
fn init --runtime node embed_func
This creates a new Node.js boilerplate function located in the embed_func
directory (the actual name is not important you can choose whatever you like). Now go to this directory and edit the func.js
file and put Mike's code there.
cd embed_func
vim func.js
- do some vim magic
- try to exit vim
I don't feel brave enough to give directions on using vim. If you don't know how to use vim but value your life or your reason, find someone who knows it.
But because I know that many wouldn't trust me anyways, I can say that to start editing the text you press i
on the keyboard (note -- INSERT -- in the bottom of the screen) then to save your changes and exit press Esc
(-- INSERT -- disappears) and type :wq
and press Enter
. To exit without saving type :q!
and to save without exiting - :w
. Read more about it here: 10 Ways to Exit Vim Editor
Most likely, after you created a new node function, pasted Mike's code and deployed it, it won't work and you'll get this message: Error invoking function. status: 504 message: Container failed to initialize, please ensure you are using the latest fdk and check the logs
I'm not a Node.js pro, but I found that installing NOT the latest version of the node-fetch
package helps.
cd embed_func
npm install [email protected]
At the moment of writing this, the latest stable version of this package is 3.2.10: https://www.npmjs.com/package/node-fetch. I didn't test absolutely all versions, but the latest 2.x version seems to be fine and the latest 3.x version doesn't work.
If everything was done correctly and you managed to exit vim, you can run the function and get the token.
fn invoke <YOUR APP NAME> <YOUR FUNCTION NAME>
This should give you a token every time you run this. If it doesn't, fix the problem first before moving on.
Oracle API Gateway
API Gateway allows for easier and safer use of the token.
Just like Functions, the API Gateways is not an essential part. I mean after (if) we decided to use Oracle Functions, it makes sense to also use Gateways. Setting up a gateway to call a function only takes a few minutes, no coding is required and things like CORS or HTTPS are handled automatically. With this said API Gateways is a no-brainer.
In nutshell, we create an URL and every time we call that URL we get a token. It is somewhat similar to where we started. If you remember, the first step was "creating" an URL that we could call and get a token. The main and significant difference is that now all details like login and password are safely hidden behind the API Gateway and Oracle Functions.
Before Functions and Gateway it was:
curl --request POST \
--url https://<IDCS-domain>.identity.oraclecloud.com/oauth2/v1/token \
--header 'authorization: Basic <base64 encoded clientID:ClientSecret>' \
--header 'content-type: application/x-www-form-urlencoded;charset=UTF-8' \
-d 'grant_type=password&username=<username>&password=<password>&scope=\
<scope copied from resource section in IDCS confidential application>'
With API Gateways the same result can be achieved by:
curl --request https://<gateway>.oci.customer-oci.com/<prefix>/<path>
Note, that there are no longer details like login and password, clientID and ClientSecret for the Secure application, or internal IDs. Everything is safely hidden behind closed doors.
API Gateways can be accessed via the Developer Services -> [API Management] Gateways menu.
We click Create Gateway and fill in some very self-explanatory properties like name or network. Note, that this URL will be called from the Internet (assuming that you are doing this to embed OAC content into a public site) so you must select the network accordingly.
After a gateway is created, go to Deployments and create one or more, well, deployments. In our case deployment is a call of our previously created function.
There are a few things to mention here.
Name is simply a marker for you so you can distinguish one deployment from another. It can be virtually anything you like.
Path prefix is the actual part of the URL. This has to follow rather strict URL rules.
The other very important thing is CORS. At the beginning of this blog I already mentioned CORS but that time it was a fake CORS message. This time CORS is actually important.
If we are embeddig OAC content into the site called https://thirdparty.com, we must add a CORS policy allowing us to do so.
If we don't do it, we will get an actual true authentic CORS error (the Network tab of the browser console):
The other very likely problem is after you created a working function, exited vim, created a gateway and deployment, and defined a deployment, you are trying to test it and get an error message {"code":500,"message":"Internal Server Error"}
:
If you are getting this error, it is possible that the problem is caused by a missing policy:
Go to
And create policy like this:
ALLOW any-user to use functions-family in compartment <INSERT YOUR COMPARTMENT HERE> where ALL { request.principal.type= 'ApiGateway'}
A few minor things
It is rather easy to copy pieces of embedding code from the Developer menu. However, by default this menu option is disabled.
It can be enabled in the profile. Click your profile icon, open Profile then Advanced and Enable Developer Options. It is mentioned in the documentation but let's be real: nobody reads it.
If you simply take the embedding script, it won't work.
This code lacks two important modules: jquery
and obitech-application/application
. If either of them is missing you will get this error: Uncaught TypeError: application.setSecurityConfig is not a function
. And by the way, the order of these modules is not exactly random. If you put them in an incorrect order, you will likely get the same error.
As a conclusion
After walking this path with a million ways to die we get this beautifully looking page: Niðurstaða stafrænna húsnæðisáætlana 2022