Skip to content
This repository has been archived by the owner on Jan 10, 2023. It is now read-only.

Add elixir example #26

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
looker_embed_sso_examples
===================
## Looker Embed SSO Examples
Example code to use the Ruby Embed SSO API in various languages

- [C#](/csharp_example.cs)
- [Elixir](/elixir)
- [Java](/LookerEmbedClientExample.java)
- [PHP](/sso_embed.php)
- [NodeJS](/node_example.js)
- [Ruby](/ruby_example.rb)

### [Link to offical Looker Single Sign-on Embedding documentation](https://docs.looker.com/reference/embedding/sso-embed)

### [Link to SSO URL build testing tool](https://fabio-looker.github.io/looker_sso_tool)

2 changes: 2 additions & 0 deletions elixir/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_build
deps
23 changes: 23 additions & 0 deletions elixir/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
### Usage

```
$ cd elixir
$ mix deps.get
$ mix deps.compile
$ mix run elixir_example.exs
```

### Options

|*Key* | *Required* | *Default* | *Description* |
|:--------------------|:-----------|:---------------|-------------------------------------------------------------------------------------------|
| `embed_url` | Yes | NA | Looker relative embed url |
| `session_length` | No | 1800 (30 mins) | The login session lenght (validiy of the SSO URL). Default: 15 mins |
| `host` | Yes | NA | Looker host |
| `secret` | Yes | NA | Looker API secret |
| `user` | Yes | NA | A Map of user data (id, first_name, last_name). This will be used to create embed user |
| `permissions` | No | NA | A list of looker permissions the embed user should have |
| `models` | No | NA | A list of looker models that should be accessible by the embed user |
| `group_ids` | No | NA | A list of looker group ids that the embed user should be added |
| `external_group_id` | No | NA | External group id for the embed user |
| `user_attributes` | No | NA | A Map of user filters/attributes that are applicable for the embed user |
133 changes: 133 additions & 0 deletions elixir/elixir_example.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
defmodule LookerEmbed do
@moduledoc """
Module for generating Looker embed SSO URL
"""

@doc """
opts[:embed_url] Looker relative embed url
opts[:session_length] The login session lenght (validiy of the SSO URL). Default: 30 mins
opts[:host] Looker host
opts[:secret] Looker API embed secret
opts[:user] A Map of user data (id, first_name, last_name). This will be used to create embed user
opts[:permissions] A list of looker permissions the embed user should have
opts[:models] A list of looker models that should be accessible by the embed user
opts[:group_ids] A list of looker group ids that the embed user should be added
opts[:external_group_id] External group id for the embed user
opts[:user_attributes] A Map of user filters/attributes that are applicable for the embed user

"""
def generate_sso_url(%{user: user} = opts) do
session_length = opts[:session_length] || (30 * 60)
host = opts.host

embed_path = "/login/embed/#{URI.encode_www_form(opts.embed_url)}"

url_options = %{
host: host,
secret: opts.secret,
external_user_id: user.id |> wrap_quotes,
first_name: user.first_name |> wrap_quotes,
last_name: user.last_name |> wrap_quotes,
permissions: opts.permissions,
models: opts.models,
group_ids: opts.group_ids,
external_group_id: opts.external_group_id |> wrap_quotes,
user_attributes: opts.user_attributes,
access_filters: %{}, # we pass empty map because looker requires this parameter
session_length: session_length |> to_string,
embed_path: embed_path,
nonce: SecureRandom.urlsafe_base64(16) |> wrap_quotes,
time: DateTime.utc_now |> DateTime.to_unix |> to_string
}

query_string = get_query_string(url_options)

"https://#{host}#{embed_path}?#{query_string}"
end

# private

defp get_signature(opts) do
string_data = "#{opts[:host]}\n"
string_data = string_data <> opts[:embed_path] <> "\n"
string_data = string_data <> opts[:nonce] <> "\n"
string_data = string_data <> opts[:time] <> "\n"
string_data = string_data <> opts[:session_length] <> "\n"
string_data = string_data <> opts[:external_user_id] <> "\n"
string_data = string_data <> encode_json(opts[:permissions]) <> "\n"
string_data = string_data <> encode_json(opts[:models]) <> "\n"

# attributes supported in new looker api version
string_data = if is_nil(opts[:group_ids]) do
string_data
else
string_data <> encode_json(opts[:group_ids]) <> "\n"
end

string_data = if is_nil(opts[:external_group_id]) do
string_data
else
string_data <> opts[:external_group_id] <> "\n"
end

string_data = string_data <> encode_json(opts[:user_attributes]) <> "\n"
string_data = string_data <> encode_json(opts[:access_filters])

:crypto.hmac(:sha, opts[:secret], string_data)
|> Base.encode64
end

defp get_query_string(opts) do
params = %{
nonce: opts.nonce,
time: opts.time,
session_length: opts.session_length,
external_user_id: opts.external_user_id,
permissions: encode_json(opts.permissions),
models: encode_json(opts.models),
access_filters: encode_json(opts.access_filters),
first_name: opts.first_name,
last_name: opts.last_name,
signature: get_signature(opts),
group_ids: encode_json(opts.group_ids),
external_group_id: opts.external_group_id,
user_attributes: encode_json(opts.user_attributes),
force_logout_login: true
}

params |> URI.encode_query
# Note: URI.encode query does not wrap values of query string in a quote.
# this creates issues as looker expects string values to be inside quote.
# For example, a map %{name: "Test"} will be encoded to [name=Taher]
# but looker wants it to be, [name="Taher"]
# The wrapping on quotes is already done at source in `generate_sso_url`
end

defp encode_json(value) when not is_nil(value) do
Poison.encode!(value)
end

defp encode_json(_), do: ""

# This is function wraps values inside double quotes.
# This is required for query string for looker as its very strict in format
defp wrap_quotes(value), do: "\"#{value}\""
end

options = %{
embed_url: "/embed/looker_report",
host: "app.looker.com",
secret: "mysekret",
user: %{
id: 100,
first_name: "Taher",
last_name: "Dhilawala"
},
permissions: ~w(access_data see_looks)s,
models: "external",
group_ids: [1],
external_group_id: 2,
user_attributes: []
}

IO.puts LookerEmbed.generate_sso_url(options)
31 changes: 31 additions & 0 deletions elixir/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
defmodule LookerEmbed.Mixfile do
use Mix.Project

def project do
[
app: :looker_embed,
version: "0.0.1",
elixir: "~> 1.5",
deps: deps()
]
end


# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
def application do
[]
end

# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
[
{:poison, "~> 2.2"},
{:secure_random, "~> 0.5"},
{:timex, "~> 3.1"}
]
end
end
14 changes: 14 additions & 0 deletions elixir/mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
%{"certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [], [], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [], [], "hexpm"},
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [], [], "hexpm"},
"hackney": {:hex, :hackney, "1.8.6", "21a725db3569b3fb11a6af17d5c5f654052ce9624219f1317e8639183de4a423", [], [{:certifi, "1.2.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "0.11.2", "9e59f17a473ef6948f63c51db07320477bad8ba88cf1df60a3eee01150306665", [], [{:hackney, "~> 1.8.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.0.2", "ac203208ada855d95dc591a764b6e87259cb0e2a364218f215ad662daa8cd6b4", [], [{:unicode_util_compat, "0.2.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [], [], "hexpm"},
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [], [], "hexpm"},
"secure_random": {:hex, :secure_random, "0.5.1", "c5532b37c89d175c328f5196a0c2a5680b15ebce3e654da37129a9fe40ebf51b", [], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [], [], "hexpm"},
"timex": {:hex, :timex, "3.1.24", "d198ae9783ac807721cca0c5535384ebdf99da4976be8cefb9665a9262a1e9e3", [], [{:combine, "~> 0.7", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.12", "1c17b68692c6ba5b6ab15db3d64cc8baa0f182043d5ae9d4b6d35d70af76f67b", [], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.2.0", "dbbccf6781821b1c0701845eaf966c9b6d83d7c3bfc65ca2b78b88b8678bfa35", [], [], "hexpm"}}