mirror of
https://github.com/datarhei/core.git
synced 2025-10-16 04:50:44 +08:00
Add /api/v3/iam/user endpoints
This commit is contained in:
@@ -444,38 +444,38 @@ func (a *api) start() error {
|
|||||||
// Create default policies for anonymous users in order to mimic
|
// Create default policies for anonymous users in order to mimic
|
||||||
// the behaviour before IAM
|
// the behaviour before IAM
|
||||||
|
|
||||||
iam.RemovePolicy("$anon", "$none", "", "")
|
iam.RemovePolicy("$anon", "$none", "", nil)
|
||||||
iam.RemovePolicy("$localhost", "$none", "", "")
|
iam.RemovePolicy("$localhost", "$none", "", nil)
|
||||||
|
|
||||||
iam.AddPolicy("$anon", "$none", "fs:/**", "GET|HEAD|OPTIONS")
|
iam.AddPolicy("$anon", "$none", "fs:/**", []string{"GET", "HEAD", "OPTIONS"})
|
||||||
iam.AddPolicy("$anon", "$none", "api:/api", "GET|HEAD|OPTIONS")
|
iam.AddPolicy("$anon", "$none", "api:/api", []string{"GET", "HEAD", "OPTIONS"})
|
||||||
iam.AddPolicy("$anon", "$none", "api:/api/v3/widget/process/**", "GET|HEAD|OPTIONS")
|
iam.AddPolicy("$anon", "$none", "api:/api/v3/widget/process/**", []string{"GET", "HEAD", "OPTIONS"})
|
||||||
|
|
||||||
iam.AddPolicy("$localhost", "$none", "api:/api", "GET|HEAD|OPTIONS")
|
iam.AddPolicy("$localhost", "$none", "api:/api", []string{"GET", "HEAD", "OPTIONS"})
|
||||||
iam.AddPolicy("$localhost", "$none", "api:/api/v3/widget/process/**", "GET|HEAD|OPTIONS")
|
iam.AddPolicy("$localhost", "$none", "api:/api/v3/widget/process/**", []string{"GET", "HEAD", "OPTIONS"})
|
||||||
|
|
||||||
if !cfg.API.Auth.Enable {
|
if !cfg.API.Auth.Enable {
|
||||||
iam.AddPolicy("$anon", "$none", "api:/api/**", "ANY")
|
iam.AddPolicy("$anon", "$none", "api:/api/**", []string{"ANY"})
|
||||||
iam.AddPolicy("$anon", "$none", "process:*", "ANY")
|
iam.AddPolicy("$anon", "$none", "process:*", []string{"ANY"})
|
||||||
iam.AddPolicy("$localhost", "$none", "api:/api/**", "ANY")
|
iam.AddPolicy("$localhost", "$none", "api:/api/**", []string{"ANY"})
|
||||||
iam.AddPolicy("$localhost", "$none", "process:*", "ANY")
|
iam.AddPolicy("$localhost", "$none", "process:*", []string{"ANY"})
|
||||||
} else {
|
} else {
|
||||||
if cfg.API.Auth.DisableLocalhost {
|
if cfg.API.Auth.DisableLocalhost {
|
||||||
iam.AddPolicy("$localhost", "$none", "api:/api/**", "ANY")
|
iam.AddPolicy("$localhost", "$none", "api:/api/**", []string{"ANY"})
|
||||||
iam.AddPolicy("$localhost", "$none", "process:*", "ANY")
|
iam.AddPolicy("$localhost", "$none", "process:*", []string{"ANY"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.Storage.Memory.Auth.Enable {
|
if !cfg.Storage.Memory.Auth.Enable {
|
||||||
iam.AddPolicy("$anon", "$none", "fs:/memfs/**", "ANY")
|
iam.AddPolicy("$anon", "$none", "fs:/memfs/**", []string{"ANY"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.RTMP.Enable && len(cfg.RTMP.Token) == 0 {
|
if cfg.RTMP.Enable && len(cfg.RTMP.Token) == 0 {
|
||||||
iam.AddPolicy("$anon", "$none", "rtmp:/**", "ANY")
|
iam.AddPolicy("$anon", "$none", "rtmp:/**", []string{"ANY"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.SRT.Enable && len(cfg.SRT.Token) == 0 {
|
if cfg.SRT.Enable && len(cfg.SRT.Token) == 0 {
|
||||||
iam.AddPolicy("$anon", "$none", "srt:**", "ANY")
|
iam.AddPolicy("$anon", "$none", "srt:**", []string{"ANY"})
|
||||||
}
|
}
|
||||||
|
|
||||||
a.iam = iam
|
a.iam = iam
|
||||||
@@ -672,9 +672,9 @@ func (a *api) start() error {
|
|||||||
var identity iam.IdentityVerifier = nil
|
var identity iam.IdentityVerifier = nil
|
||||||
|
|
||||||
if len(config.Owner) == 0 {
|
if len(config.Owner) == 0 {
|
||||||
identity, _ = a.iam.GetDefaultIdentity()
|
identity, _ = a.iam.GetDefaultVerifier()
|
||||||
} else {
|
} else {
|
||||||
identity, _ = a.iam.GetIdentity(config.Owner)
|
identity, _ = a.iam.GetVerifier(config.Owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
if identity != nil {
|
if identity != nil {
|
||||||
@@ -698,9 +698,9 @@ func (a *api) start() error {
|
|||||||
var identity iam.IdentityVerifier = nil
|
var identity iam.IdentityVerifier = nil
|
||||||
|
|
||||||
if len(config.Owner) == 0 {
|
if len(config.Owner) == 0 {
|
||||||
identity, _ = a.iam.GetDefaultIdentity()
|
identity, _ = a.iam.GetDefaultVerifier()
|
||||||
} else {
|
} else {
|
||||||
identity, _ = a.iam.GetIdentity(config.Owner)
|
identity, _ = a.iam.GetVerifier(config.Owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
if identity != nil {
|
if identity != nil {
|
||||||
|
@@ -1440,7 +1440,7 @@ func probeInput(binary string, config app.Config) app.Probe {
|
|||||||
Logger: nil,
|
Logger: nil,
|
||||||
})
|
})
|
||||||
|
|
||||||
iam.AddPolicy("$anon", "$none", "process:*", "CREATE|GET|DELETE|PROBE")
|
iam.AddPolicy("$anon", "$none", "process:*", []string{"CREATE", "GET", "DELETE", "PROBE"})
|
||||||
|
|
||||||
rs, err := restream.New(restream.Config{
|
rs, err := restream.New(restream.Config{
|
||||||
FFmpeg: ffmpeg,
|
FFmpeg: ffmpeg,
|
||||||
|
406
docs/docs.go
406
docs/docs.go
@@ -1,5 +1,4 @@
|
|||||||
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
// Code generated by swaggo/swag. DO NOT EDIT
|
||||||
// This file was generated by swaggo/swag
|
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import "github.com/swaggo/swag"
|
import "github.com/swaggo/swag"
|
||||||
@@ -110,87 +109,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/login": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Auth0KeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Retrieve valid JWT access and refresh tokens to use for accessing the API. Login either by username/password or Auth0 token",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Retrieve an access and a refresh token",
|
|
||||||
"operationId": "jwt-login",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "Login data",
|
|
||||||
"name": "data",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Login"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.JWT"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "Forbidden",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/login/refresh": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiRefreshKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Retrieve a new access token by providing the refresh token",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Retrieve a new access token",
|
|
||||||
"operationId": "jwt-refresh",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.JWTRefresh"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/swagger": {
|
"/api/swagger": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Swagger UI for this API",
|
"description": "Swagger UI for this API",
|
||||||
@@ -550,6 +468,207 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v3/iam/user": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Add a new user",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"v16.?.?"
|
||||||
|
],
|
||||||
|
"summary": "Add a new user",
|
||||||
|
"operationId": "iam-3-add-user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "User definition",
|
||||||
|
"name": "config",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v3/iam/user/{name}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List aa user by its name",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"v16.?.?"
|
||||||
|
],
|
||||||
|
"summary": "List an user by its name",
|
||||||
|
"operationId": "iam-3-get-user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Replace an existing user.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"v16.?.?"
|
||||||
|
],
|
||||||
|
"summary": "Replace an existing user",
|
||||||
|
"operationId": "iam-3-update-user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "User definition",
|
||||||
|
"name": "user",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Delete an user by its name",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"v16.?.?"
|
||||||
|
],
|
||||||
|
"summary": "Delete an user by its name",
|
||||||
|
"operationId": "iam-3-delete-user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v3/log": {
|
"/api/v3/log": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2690,22 +2809,115 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.JWT": {
|
"api.IAMAuth0Tenant": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"access_token": {
|
"audience": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"refresh_token": {
|
"client_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"domain": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.JWTRefresh": {
|
"api.IAMPolicy": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"access_token": {
|
"actions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUser": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"auth": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuth"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"policies": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/api.IAMPolicy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"superuser": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuth": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"api": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthAPI"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthServices"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuthAPI": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"auth0": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthAPIAuth0"
|
||||||
|
},
|
||||||
|
"userpass": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthPassword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuthAPIAuth0": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"tenant": {
|
||||||
|
"$ref": "#/definitions/api.IAMAuth0Tenant"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuthPassword": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuthServices": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"basic": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthPassword"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2713,21 +2925,6 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"api.Login": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"password",
|
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"password": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"username": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api.MetricsDescription": {
|
"api.MetricsDescription": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -3043,6 +3240,9 @@ const docTemplate = `{
|
|||||||
"autostart": {
|
"autostart": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"group": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@@ -102,87 +102,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/login": {
|
|
||||||
"post": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Auth0KeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Retrieve valid JWT access and refresh tokens to use for accessing the API. Login either by username/password or Auth0 token",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Retrieve an access and a refresh token",
|
|
||||||
"operationId": "jwt-login",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "Login data",
|
|
||||||
"name": "data",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Login"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.JWT"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "Forbidden",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/login/refresh": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiRefreshKeyAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Retrieve a new access token by providing the refresh token",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Retrieve a new access token",
|
|
||||||
"operationId": "jwt-refresh",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.JWTRefresh"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/swagger": {
|
"/api/swagger": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Swagger UI for this API",
|
"description": "Swagger UI for this API",
|
||||||
@@ -542,6 +461,207 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v3/iam/user": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Add a new user",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"v16.?.?"
|
||||||
|
],
|
||||||
|
"summary": "Add a new user",
|
||||||
|
"operationId": "iam-3-add-user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "User definition",
|
||||||
|
"name": "config",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v3/iam/user/{name}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List aa user by its name",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"v16.?.?"
|
||||||
|
],
|
||||||
|
"summary": "List an user by its name",
|
||||||
|
"operationId": "iam-3-get-user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Replace an existing user.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"v16.?.?"
|
||||||
|
],
|
||||||
|
"summary": "Replace an existing user",
|
||||||
|
"operationId": "iam-3-update-user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "User definition",
|
||||||
|
"name": "user",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.IAMUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Delete an user by its name",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"v16.?.?"
|
||||||
|
],
|
||||||
|
"summary": "Delete an user by its name",
|
||||||
|
"operationId": "iam-3-delete-user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v3/log": {
|
"/api/v3/log": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2682,22 +2802,115 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.JWT": {
|
"api.IAMAuth0Tenant": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"access_token": {
|
"audience": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"refresh_token": {
|
"client_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"domain": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api.JWTRefresh": {
|
"api.IAMPolicy": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"access_token": {
|
"actions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUser": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"auth": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuth"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"policies": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/api.IAMPolicy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"superuser": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuth": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"api": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthAPI"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthServices"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuthAPI": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"auth0": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthAPIAuth0"
|
||||||
|
},
|
||||||
|
"userpass": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthPassword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuthAPIAuth0": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"tenant": {
|
||||||
|
"$ref": "#/definitions/api.IAMAuth0Tenant"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuthPassword": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.IAMUserAuthServices": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"basic": {
|
||||||
|
"$ref": "#/definitions/api.IAMUserAuthPassword"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2705,21 +2918,6 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"api.Login": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"password",
|
|
||||||
"username"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"password": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"username": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api.MetricsDescription": {
|
"api.MetricsDescription": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -3035,6 +3233,9 @@
|
|||||||
"autostart": {
|
"autostart": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"group": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@@ -469,31 +469,81 @@ definitions:
|
|||||||
items: {}
|
items: {}
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
api.JWT:
|
api.IAMAuth0Tenant:
|
||||||
properties:
|
properties:
|
||||||
access_token:
|
audience:
|
||||||
type: string
|
type: string
|
||||||
refresh_token:
|
client_id:
|
||||||
|
type: string
|
||||||
|
domain:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
api.JWTRefresh:
|
api.IAMPolicy:
|
||||||
properties:
|
properties:
|
||||||
access_token:
|
actions:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
group:
|
||||||
type: string
|
type: string
|
||||||
|
resource:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
api.IAMUser:
|
||||||
|
properties:
|
||||||
|
auth:
|
||||||
|
$ref: '#/definitions/api.IAMUserAuth'
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
policies:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/api.IAMPolicy'
|
||||||
|
type: array
|
||||||
|
superuser:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
api.IAMUserAuth:
|
||||||
|
properties:
|
||||||
|
api:
|
||||||
|
$ref: '#/definitions/api.IAMUserAuthAPI'
|
||||||
|
services:
|
||||||
|
$ref: '#/definitions/api.IAMUserAuthServices'
|
||||||
|
type: object
|
||||||
|
api.IAMUserAuthAPI:
|
||||||
|
properties:
|
||||||
|
auth0:
|
||||||
|
$ref: '#/definitions/api.IAMUserAuthAPIAuth0'
|
||||||
|
userpass:
|
||||||
|
$ref: '#/definitions/api.IAMUserAuthPassword'
|
||||||
|
type: object
|
||||||
|
api.IAMUserAuthAPIAuth0:
|
||||||
|
properties:
|
||||||
|
enable:
|
||||||
|
type: boolean
|
||||||
|
tenant:
|
||||||
|
$ref: '#/definitions/api.IAMAuth0Tenant'
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
api.IAMUserAuthPassword:
|
||||||
|
properties:
|
||||||
|
enable:
|
||||||
|
type: boolean
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
api.IAMUserAuthServices:
|
||||||
|
properties:
|
||||||
|
basic:
|
||||||
|
$ref: '#/definitions/api.IAMUserAuthPassword'
|
||||||
|
token:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
type: object
|
type: object
|
||||||
api.LogEvent:
|
api.LogEvent:
|
||||||
additionalProperties: true
|
additionalProperties: true
|
||||||
type: object
|
type: object
|
||||||
api.Login:
|
|
||||||
properties:
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- password
|
|
||||||
- username
|
|
||||||
type: object
|
|
||||||
api.MetricsDescription:
|
api.MetricsDescription:
|
||||||
properties:
|
properties:
|
||||||
description:
|
description:
|
||||||
@@ -706,6 +756,8 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
autostart:
|
autostart:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
group:
|
||||||
|
type: string
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
input:
|
input:
|
||||||
@@ -1980,58 +2032,6 @@ paths:
|
|||||||
security:
|
security:
|
||||||
- ApiKeyAuth: []
|
- ApiKeyAuth: []
|
||||||
summary: Query the GraphAPI
|
summary: Query the GraphAPI
|
||||||
/api/login:
|
|
||||||
post:
|
|
||||||
description: Retrieve valid JWT access and refresh tokens to use for accessing
|
|
||||||
the API. Login either by username/password or Auth0 token
|
|
||||||
operationId: jwt-login
|
|
||||||
parameters:
|
|
||||||
- description: Login data
|
|
||||||
in: body
|
|
||||||
name: data
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.Login'
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.JWT'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.Error'
|
|
||||||
"403":
|
|
||||||
description: Forbidden
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.Error'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.Error'
|
|
||||||
security:
|
|
||||||
- Auth0KeyAuth: []
|
|
||||||
summary: Retrieve an access and a refresh token
|
|
||||||
/api/login/refresh:
|
|
||||||
get:
|
|
||||||
description: Retrieve a new access token by providing the refresh token
|
|
||||||
operationId: jwt-refresh
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.JWTRefresh'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.Error'
|
|
||||||
security:
|
|
||||||
- ApiRefreshKeyAuth: []
|
|
||||||
summary: Retrieve a new access token
|
|
||||||
/api/swagger:
|
/api/swagger:
|
||||||
get:
|
get:
|
||||||
description: Swagger UI for this API
|
description: Swagger UI for this API
|
||||||
@@ -2266,6 +2266,135 @@ paths:
|
|||||||
security:
|
security:
|
||||||
- ApiKeyAuth: []
|
- ApiKeyAuth: []
|
||||||
summary: Add a file to a filesystem
|
summary: Add a file to a filesystem
|
||||||
|
/api/v3/iam/user:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Add a new user
|
||||||
|
operationId: iam-3-add-user
|
||||||
|
parameters:
|
||||||
|
- description: User definition
|
||||||
|
in: body
|
||||||
|
name: config
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.IAMUser'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.IAMUser'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Add a new user
|
||||||
|
tags:
|
||||||
|
- v16.?.?
|
||||||
|
/api/v3/iam/user/{name}:
|
||||||
|
delete:
|
||||||
|
description: Delete an user by its name
|
||||||
|
operationId: iam-3-delete-user
|
||||||
|
parameters:
|
||||||
|
- description: Username
|
||||||
|
in: path
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Delete an user by its name
|
||||||
|
tags:
|
||||||
|
- v16.?.?
|
||||||
|
get:
|
||||||
|
description: List aa user by its name
|
||||||
|
operationId: iam-3-get-user
|
||||||
|
parameters:
|
||||||
|
- description: Username
|
||||||
|
in: path
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.IAMUser'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: List an user by its name
|
||||||
|
tags:
|
||||||
|
- v16.?.?
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Replace an existing user.
|
||||||
|
operationId: iam-3-update-user
|
||||||
|
parameters:
|
||||||
|
- description: Username
|
||||||
|
in: path
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: User definition
|
||||||
|
in: body
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.IAMUser'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.IAMUser'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Replace an existing user
|
||||||
|
tags:
|
||||||
|
- v16.?.?
|
||||||
/api/v3/log:
|
/api/v3/log:
|
||||||
get:
|
get:
|
||||||
description: Get the last log lines of the Restreamer application
|
description: Get the last log lines of the Restreamer application
|
||||||
|
125
http/api/iam.go
Normal file
125
http/api/iam.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import "github.com/datarhei/core/v16/iam"
|
||||||
|
|
||||||
|
type IAMUser struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Superuser bool `json:"superuser"`
|
||||||
|
Auth IAMUserAuth `json:"auth"`
|
||||||
|
Policies []IAMPolicy `json:"policies"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *IAMUser) Marshal(user iam.User, policies []iam.Policy) {
|
||||||
|
u.Name = user.Name
|
||||||
|
u.Superuser = user.Superuser
|
||||||
|
u.Auth = IAMUserAuth{
|
||||||
|
API: IAMUserAuthAPI{
|
||||||
|
Userpass: IAMUserAuthPassword{
|
||||||
|
Enable: user.Auth.API.Userpass.Enable,
|
||||||
|
Password: user.Auth.API.Userpass.Password,
|
||||||
|
},
|
||||||
|
Auth0: IAMUserAuthAPIAuth0{
|
||||||
|
Enable: false,
|
||||||
|
User: "",
|
||||||
|
Tenant: IAMAuth0Tenant{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: IAMUserAuthServices{
|
||||||
|
Basic: IAMUserAuthPassword{
|
||||||
|
Enable: user.Auth.Services.Basic.Enable,
|
||||||
|
Password: user.Auth.Services.Basic.Password,
|
||||||
|
},
|
||||||
|
Token: user.Auth.Services.Token,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range policies {
|
||||||
|
u.Policies = append(u.Policies, IAMPolicy{
|
||||||
|
Domain: p.Domain,
|
||||||
|
Resource: p.Resource,
|
||||||
|
Actions: p.Actions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *IAMUser) Unmarshal() (iam.User, []iam.Policy) {
|
||||||
|
iamuser := iam.User{
|
||||||
|
Name: u.Name,
|
||||||
|
Superuser: u.Superuser,
|
||||||
|
Auth: iam.UserAuth{
|
||||||
|
API: iam.UserAuthAPI{
|
||||||
|
Userpass: iam.UserAuthPassword{
|
||||||
|
Enable: u.Auth.API.Userpass.Enable,
|
||||||
|
Password: u.Auth.API.Userpass.Password,
|
||||||
|
},
|
||||||
|
Auth0: iam.UserAuthAPIAuth0{
|
||||||
|
Enable: u.Auth.API.Auth0.Enable,
|
||||||
|
User: u.Auth.API.Auth0.User,
|
||||||
|
Tenant: iam.Auth0Tenant{
|
||||||
|
Domain: u.Auth.API.Auth0.Tenant.Domain,
|
||||||
|
Audience: u.Auth.API.Auth0.Tenant.Audience,
|
||||||
|
ClientID: u.Auth.API.Auth0.Tenant.ClientID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: iam.UserAuthServices{
|
||||||
|
Basic: iam.UserAuthPassword{
|
||||||
|
Enable: u.Auth.Services.Basic.Enable,
|
||||||
|
Password: u.Auth.Services.Basic.Password,
|
||||||
|
},
|
||||||
|
Token: u.Auth.Services.Token,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
iampolicies := []iam.Policy{}
|
||||||
|
|
||||||
|
for _, p := range u.Policies {
|
||||||
|
iampolicies = append(iampolicies, iam.Policy{
|
||||||
|
Name: u.Name,
|
||||||
|
Domain: p.Domain,
|
||||||
|
Resource: p.Resource,
|
||||||
|
Actions: p.Actions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return iamuser, iampolicies
|
||||||
|
}
|
||||||
|
|
||||||
|
type IAMUserAuth struct {
|
||||||
|
API IAMUserAuthAPI `json:"api"`
|
||||||
|
Services IAMUserAuthServices `json:"services"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IAMUserAuthAPI struct {
|
||||||
|
Userpass IAMUserAuthPassword `json:"userpass"`
|
||||||
|
Auth0 IAMUserAuthAPIAuth0 `json:"auth0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IAMUserAuthAPIAuth0 struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Tenant IAMAuth0Tenant `json:"tenant"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IAMUserAuthServices struct {
|
||||||
|
Basic IAMUserAuthPassword `json:"basic"`
|
||||||
|
Token []string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IAMUserAuthPassword struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IAMAuth0Tenant struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Audience string `json:"audience"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IAMPolicy struct {
|
||||||
|
Domain string `json:"group"`
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Actions []string `json:"actions"`
|
||||||
|
}
|
170
http/handler/api/iam.go
Normal file
170
http/handler/api/iam.go
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
"github.com/datarhei/core/v16/http/handler/util"
|
||||||
|
"github.com/datarhei/core/v16/iam"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IAMHandler struct {
|
||||||
|
iam iam.IAM
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIAM(iam iam.IAM) *IAMHandler {
|
||||||
|
return &IAMHandler{
|
||||||
|
iam: iam,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a new user
|
||||||
|
// @Summary Add a new user
|
||||||
|
// @Description Add a new user
|
||||||
|
// @Tags v16.?.?
|
||||||
|
// @ID iam-3-add-user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param config body api.IAMUser true "User definition"
|
||||||
|
// @Success 200 {object} api.IAMUser
|
||||||
|
// @Failure 400 {object} api.Error
|
||||||
|
// @Failure 500 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/iam/user [post]
|
||||||
|
func (h *IAMHandler) AddUser(c echo.Context) error {
|
||||||
|
//user := util.DefaultContext(c, "user", "")
|
||||||
|
|
||||||
|
user := api.IAMUser{}
|
||||||
|
|
||||||
|
if err := util.ShouldBindJSON(c, &user); err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iamuser, iampolicies := user.Unmarshal()
|
||||||
|
|
||||||
|
err := h.iam.CreateIdentity(iamuser)
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Bad request", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range iampolicies {
|
||||||
|
h.iam.AddPolicy(p.Name, p.Domain, p.Resource, p.Actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.iam.SaveIdentities()
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusInternalServerError, "Internal server error", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the user with the given name
|
||||||
|
// @Summary Delete an user by its name
|
||||||
|
// @Description Delete an user by its name
|
||||||
|
// @Tags v16.?.?
|
||||||
|
// @ID iam-3-delete-user
|
||||||
|
// @Produce json
|
||||||
|
// @Param name path string true "Username"
|
||||||
|
// @Success 200 {string} string
|
||||||
|
// @Failure 404 {object} api.Error
|
||||||
|
// @Failure 500 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/iam/user/{name} [delete]
|
||||||
|
func (h *IAMHandler) RemoveUser(c echo.Context) error {
|
||||||
|
name := util.PathParam(c, "name")
|
||||||
|
|
||||||
|
err := h.iam.DeleteIdentity(name)
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Bad request", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.iam.SaveIdentities()
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusInternalServerError, "Internal server error", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update replaces an existing user
|
||||||
|
// @Summary Replace an existing user
|
||||||
|
// @Description Replace an existing user.
|
||||||
|
// @Tags v16.?.?
|
||||||
|
// @ID iam-3-update-user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param name path string true "Username"
|
||||||
|
// @Param user body api.IAMUser true "User definition"
|
||||||
|
// @Success 200 {object} api.IAMUser
|
||||||
|
// @Failure 400 {object} api.Error
|
||||||
|
// @Failure 404 {object} api.Error
|
||||||
|
// @Failure 500 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/iam/user/{name} [put]
|
||||||
|
func (h *IAMHandler) UpdateUser(c echo.Context) error {
|
||||||
|
name := util.PathParam(c, "name")
|
||||||
|
|
||||||
|
iamuser, err := h.iam.GetIdentity(name)
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusNotFound, "Not found", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iampolicies := h.iam.ListPolicies(name, "", "", nil)
|
||||||
|
|
||||||
|
user := api.IAMUser{}
|
||||||
|
user.Marshal(iamuser, iampolicies)
|
||||||
|
|
||||||
|
if err := util.ShouldBindJSON(c, &user); err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iamuser, iampolicies = user.Unmarshal()
|
||||||
|
|
||||||
|
err = h.iam.UpdateIdentity(name, iamuser)
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Bad request", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.iam.RemovePolicy(name, "", "", nil)
|
||||||
|
|
||||||
|
for _, p := range iampolicies {
|
||||||
|
h.iam.AddPolicy(p.Name, p.Domain, p.Resource, p.Actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.iam.SaveIdentities()
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusInternalServerError, "Internal server error", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the user with the given name
|
||||||
|
// @Summary List an user by its name
|
||||||
|
// @Description List aa user by its name
|
||||||
|
// @Tags v16.?.?
|
||||||
|
// @ID iam-3-get-user
|
||||||
|
// @Produce json
|
||||||
|
// @Param name path string true "Username"
|
||||||
|
// @Success 200 {object} api.IAMUser
|
||||||
|
// @Failure 404 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/iam/user/{name} [get]
|
||||||
|
func (h *IAMHandler) GetUser(c echo.Context) error {
|
||||||
|
name := util.PathParam(c, "name")
|
||||||
|
|
||||||
|
iamuser, err := h.iam.GetIdentity(name)
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusNotFound, "Not found", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iampolicies := h.iam.ListPolicies(name, "", "", nil)
|
||||||
|
|
||||||
|
user := api.IAMUser{}
|
||||||
|
user.Marshal(iamuser, iampolicies)
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, user)
|
||||||
|
}
|
@@ -250,7 +250,7 @@ func (m *iammiddleware) findIdentityFromBasicAuth(c echo.Context) (iam.IdentityV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := m.iam.GetIdentity(username)
|
identity, err := m.iam.GetVerifier(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Debug().WithFields(log.Fields{
|
m.logger.Debug().WithFields(log.Fields{
|
||||||
"path": c.Request().URL.Path,
|
"path": c.Request().URL.Path,
|
||||||
@@ -314,7 +314,7 @@ func (m *iammiddleware) findIdentityFromJWT(c echo.Context) (iam.IdentityVerifie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := m.iam.GetIdentity(subject)
|
identity, err := m.iam.GetVerifier(subject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Debug().WithFields(log.Fields{
|
m.logger.Debug().WithFields(log.Fields{
|
||||||
"path": c.Request().URL.Path,
|
"path": c.Request().URL.Path,
|
||||||
@@ -343,7 +343,7 @@ func (m *iammiddleware) findIdentityFromUserpass(c echo.Context) (iam.IdentityVe
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := m.iam.GetIdentity(login.Username)
|
identity, err := m.iam.GetVerifier(login.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Debug().WithFields(log.Fields{
|
m.logger.Debug().WithFields(log.Fields{
|
||||||
"path": c.Request().URL.Path,
|
"path": c.Request().URL.Path,
|
||||||
@@ -400,7 +400,7 @@ func (m *iammiddleware) findIdentityFromAuth0(c echo.Context) (iam.IdentityVerif
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := m.iam.GetIdentityByAuth0(subject)
|
identity, err := m.iam.GetVerfierFromAuth0(subject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Debug().WithFields(log.Fields{
|
m.logger.Debug().WithFields(log.Fields{
|
||||||
"path": c.Request().URL.Path,
|
"path": c.Request().URL.Path,
|
||||||
|
@@ -82,7 +82,7 @@ func TestBasicAuth(t *testing.T) {
|
|||||||
iam, err := getIAM()
|
iam, err := getIAM()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
iam.AddPolicy("foobar", "$none", "fs:/**", "ANY")
|
iam.AddPolicy("foobar", "$none", "fs:/**", []string{"ANY"})
|
||||||
|
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
@@ -141,9 +141,9 @@ func TestFindDomainFromFilesystem(t *testing.T) {
|
|||||||
iam, err := getIAM()
|
iam, err := getIAM()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
iam.AddPolicy("$anon", "$none", "fs:/**", "ANY")
|
iam.AddPolicy("$anon", "$none", "fs:/**", []string{"ANY"})
|
||||||
iam.AddPolicy("foobar", "group", "fs:/group/**", "ANY")
|
iam.AddPolicy("foobar", "group", "fs:/group/**", []string{"ANY"})
|
||||||
iam.AddPolicy("foobar", "anothergroup", "fs:/memfs/anothergroup/**", "ANY")
|
iam.AddPolicy("foobar", "anothergroup", "fs:/memfs/anothergroup/**", []string{"ANY"})
|
||||||
|
|
||||||
mw := &iammiddleware{
|
mw := &iammiddleware{
|
||||||
iam: iam,
|
iam: iam,
|
||||||
@@ -167,8 +167,8 @@ func TestBasicAuthDomain(t *testing.T) {
|
|||||||
iam, err := getIAM()
|
iam, err := getIAM()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
iam.AddPolicy("$anon", "$none", "fs:/**", "ANY")
|
iam.AddPolicy("$anon", "$none", "fs:/**", []string{"ANY"})
|
||||||
iam.AddPolicy("foobar", "group", "fs:/group/**", "ANY")
|
iam.AddPolicy("foobar", "group", "fs:/group/**", []string{"ANY"})
|
||||||
|
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
@@ -200,7 +200,7 @@ func TestBasicAuthDomain(t *testing.T) {
|
|||||||
require.NoError(t, h(c))
|
require.NoError(t, h(c))
|
||||||
|
|
||||||
// Allow anonymous group read access
|
// Allow anonymous group read access
|
||||||
iam.AddPolicy("$anon", "group", "fs:/group/**", "GET")
|
iam.AddPolicy("$anon", "group", "fs:/group/**", []string{"GET"})
|
||||||
|
|
||||||
req.Header.Del(echo.HeaderAuthorization)
|
req.Header.Del(echo.HeaderAuthorization)
|
||||||
require.NoError(t, h(c))
|
require.NoError(t, h(c))
|
||||||
@@ -210,7 +210,7 @@ func TestAPILoginAndRefresh(t *testing.T) {
|
|||||||
iam, err := getIAM()
|
iam, err := getIAM()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
iam.AddPolicy("foobar", "$none", "api:/**", "ANY")
|
iam.AddPolicy("foobar", "$none", "api:/**", []string{"ANY"})
|
||||||
|
|
||||||
jwthandler := apihandler.NewJWT(iam)
|
jwthandler := apihandler.NewJWT(iam)
|
||||||
|
|
||||||
|
@@ -66,8 +66,8 @@ func DummyRestreamer(pathPrefix string) (restream.Restreamer, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
iam.AddPolicy("$anon", "$none", "api:/**", "ANY")
|
iam.AddPolicy("$anon", "$none", "api:/**", []string{"ANY"})
|
||||||
iam.AddPolicy("$anon", "$none", "fs:/**", "ANY")
|
iam.AddPolicy("$anon", "$none", "fs:/**", []string{"ANY"})
|
||||||
|
|
||||||
rs, err := restream.New(restream.Config{
|
rs, err := restream.New(restream.Config{
|
||||||
Store: store,
|
Store: store,
|
||||||
|
@@ -126,6 +126,7 @@ type server struct {
|
|||||||
session *api.SessionHandler
|
session *api.SessionHandler
|
||||||
widget *api.WidgetHandler
|
widget *api.WidgetHandler
|
||||||
resources *api.MetricsHandler
|
resources *api.MetricsHandler
|
||||||
|
iam *api.IAMHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
middleware struct {
|
middleware struct {
|
||||||
@@ -235,6 +236,8 @@ func NewServer(config Config) (Server, error) {
|
|||||||
|
|
||||||
s.handler.jwt = api.NewJWT(config.IAM)
|
s.handler.jwt = api.NewJWT(config.IAM)
|
||||||
|
|
||||||
|
s.v3handler.iam = api.NewIAM(config.IAM)
|
||||||
|
|
||||||
s.v3handler.log = api.NewLog(
|
s.v3handler.log = api.NewLog(
|
||||||
config.LogBuffer,
|
config.LogBuffer,
|
||||||
)
|
)
|
||||||
@@ -528,6 +531,14 @@ func (s *server) setRoutesV3(v3 *echo.Group) {
|
|||||||
s.router.GET("/api/v3/widget/process/:id", s.v3handler.widget.Get)
|
s.router.GET("/api/v3/widget/process/:id", s.v3handler.widget.Get)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v3 IAM
|
||||||
|
if s.v3handler.iam != nil {
|
||||||
|
v3.POST("/iam/user", s.v3handler.iam.AddUser)
|
||||||
|
v3.GET("/iam/user/:name", s.v3handler.iam.GetUser)
|
||||||
|
v3.PUT("/iam/user/:name", s.v3handler.iam.UpdateUser)
|
||||||
|
v3.DELETE("/iam/user/:name", s.v3handler.iam.RemoveUser)
|
||||||
|
}
|
||||||
|
|
||||||
// v3 Restreamer
|
// v3 Restreamer
|
||||||
if s.v3handler.restream != nil {
|
if s.v3handler.restream != nil {
|
||||||
v3.GET("/skills", s.v3handler.restream.Skills)
|
v3.GET("/skills", s.v3handler.restream.Skills)
|
||||||
|
@@ -11,6 +11,13 @@ import (
|
|||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Policy struct {
|
||||||
|
Name string
|
||||||
|
Domain string
|
||||||
|
Resource string
|
||||||
|
Actions []string
|
||||||
|
}
|
||||||
|
|
||||||
type AccessEnforcer interface {
|
type AccessEnforcer interface {
|
||||||
Enforce(name, domain, resource, action string) (bool, string)
|
Enforce(name, domain, resource, action string) (bool, string)
|
||||||
HasGroup(name string) bool
|
HasGroup(name string) bool
|
||||||
@@ -19,9 +26,9 @@ type AccessEnforcer interface {
|
|||||||
type AccessManager interface {
|
type AccessManager interface {
|
||||||
AccessEnforcer
|
AccessEnforcer
|
||||||
|
|
||||||
AddPolicy(username, domain, resource, actions string) bool
|
AddPolicy(name, domain, resource string, actions []string) bool
|
||||||
RemovePolicy(username, domain, resource, actions string) bool
|
RemovePolicy(name, domain, resource string, actions []string) bool
|
||||||
ListPolicies(username, domain, resource, actions string) [][]string
|
ListPolicies(name, domain, resource string, actions []string) []Policy
|
||||||
}
|
}
|
||||||
|
|
||||||
type access struct {
|
type access struct {
|
||||||
@@ -58,7 +65,10 @@ func NewAccessManager(config AccessConfig) (AccessManager, error) {
|
|||||||
m.AddDef("e", "e", "some(where (p.eft == allow))")
|
m.AddDef("e", "e", "some(where (p.eft == allow))")
|
||||||
m.AddDef("m", "m", `g(r.sub, p.sub, r.dom) && r.dom == p.dom && ResourceMatch(r.obj, r.dom, p.obj) && ActionMatch(r.act, p.act) || r.sub == "$superuser"`)
|
m.AddDef("m", "m", `g(r.sub, p.sub, r.dom) && r.dom == p.dom && ResourceMatch(r.obj, r.dom, p.obj) && ActionMatch(r.act, p.act) || r.sub == "$superuser"`)
|
||||||
|
|
||||||
a := newAdapter(am.fs, "./policy.json", am.logger)
|
a, err := newAdapter(am.fs, "./policy.json", am.logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
e, err := casbin.NewEnforcer(m, a)
|
e, err := casbin.NewEnforcer(m, a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -74,8 +84,8 @@ func NewAccessManager(config AccessConfig) (AccessManager, error) {
|
|||||||
return am, nil
|
return am, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *access) AddPolicy(username, domain, resource, actions string) bool {
|
func (am *access) AddPolicy(name, domain, resource string, actions []string) bool {
|
||||||
policy := []string{username, domain, resource, actions}
|
policy := []string{name, domain, resource, strings.Join(actions, "|")}
|
||||||
|
|
||||||
if am.enforcer.HasPolicy(policy) {
|
if am.enforcer.HasPolicy(policy) {
|
||||||
return true
|
return true
|
||||||
@@ -86,15 +96,28 @@ func (am *access) AddPolicy(username, domain, resource, actions string) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *access) RemovePolicy(username, domain, resource, actions string) bool {
|
func (am *access) RemovePolicy(name, domain, resource string, actions []string) bool {
|
||||||
policies := am.enforcer.GetFilteredPolicy(0, username, domain, resource, actions)
|
policies := am.enforcer.GetFilteredPolicy(0, name, domain, resource, strings.Join(actions, "|"))
|
||||||
am.enforcer.RemovePolicies(policies)
|
am.enforcer.RemovePolicies(policies)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *access) ListPolicies(username, domain, resource, actions string) [][]string {
|
func (am *access) ListPolicies(name, domain, resource string, actions []string) []Policy {
|
||||||
return am.enforcer.GetFilteredPolicy(0, username, domain, resource, actions)
|
policies := []Policy{}
|
||||||
|
|
||||||
|
ps := am.enforcer.GetFilteredPolicy(0, name, domain, resource, strings.Join(actions, "|"))
|
||||||
|
|
||||||
|
for _, p := range ps {
|
||||||
|
policies = append(policies, Policy{
|
||||||
|
Name: p[0],
|
||||||
|
Domain: p[1],
|
||||||
|
Resource: p[2],
|
||||||
|
Actions: strings.Split(p[3], "|"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return policies
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *access) HasGroup(name string) bool {
|
func (am *access) HasGroup(name string) bool {
|
||||||
|
85
iam/access_test.go
Normal file
85
iam/access_test.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package iam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/io/fs"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessManager(t *testing.T) {
|
||||||
|
memfs, err := fs.NewMemFilesystemFromDir("./fixtures", fs.MemConfig{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
am, err := NewAccessManager(AccessConfig{
|
||||||
|
FS: memfs,
|
||||||
|
Logger: nil,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
policies := am.ListPolicies("", "", "", nil)
|
||||||
|
require.ElementsMatch(t, []Policy{
|
||||||
|
{
|
||||||
|
Name: "ingo",
|
||||||
|
Domain: "$none",
|
||||||
|
Resource: "rtmp:/bla-*",
|
||||||
|
Actions: []string{"play", "publish"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ingo",
|
||||||
|
Domain: "igelcamp",
|
||||||
|
Resource: "rtmp:/igelcamp/**",
|
||||||
|
Actions: []string{"publish"},
|
||||||
|
},
|
||||||
|
}, policies)
|
||||||
|
|
||||||
|
am.AddPolicy("foobar", "group", "bla:/", []string{"write"})
|
||||||
|
|
||||||
|
policies = am.ListPolicies("", "", "", nil)
|
||||||
|
require.ElementsMatch(t, []Policy{
|
||||||
|
{
|
||||||
|
Name: "ingo",
|
||||||
|
Domain: "$none",
|
||||||
|
Resource: "rtmp:/bla-*",
|
||||||
|
Actions: []string{"play", "publish"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ingo",
|
||||||
|
Domain: "igelcamp",
|
||||||
|
Resource: "rtmp:/igelcamp/**",
|
||||||
|
Actions: []string{"publish"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "foobar",
|
||||||
|
Domain: "group",
|
||||||
|
Resource: "bla:/",
|
||||||
|
Actions: []string{"write"},
|
||||||
|
},
|
||||||
|
}, policies)
|
||||||
|
|
||||||
|
require.True(t, am.HasGroup("igelcamp"))
|
||||||
|
require.True(t, am.HasGroup("group"))
|
||||||
|
require.False(t, am.HasGroup("$none"))
|
||||||
|
|
||||||
|
am.RemovePolicy("ingo", "", "", nil)
|
||||||
|
|
||||||
|
policies = am.ListPolicies("", "", "", nil)
|
||||||
|
require.ElementsMatch(t, []Policy{
|
||||||
|
{
|
||||||
|
Name: "foobar",
|
||||||
|
Domain: "group",
|
||||||
|
Resource: "bla:/",
|
||||||
|
Actions: []string{"write"},
|
||||||
|
},
|
||||||
|
}, policies)
|
||||||
|
|
||||||
|
require.False(t, am.HasGroup("igelcamp"))
|
||||||
|
require.True(t, am.HasGroup("group"))
|
||||||
|
require.False(t, am.HasGroup("$none"))
|
||||||
|
|
||||||
|
ok, _ := am.Enforce("foobar", "group", "bla:/", "read")
|
||||||
|
require.False(t, ok)
|
||||||
|
|
||||||
|
ok, _ = am.Enforce("foobar", "group", "bla:/", "write")
|
||||||
|
require.True(t, ok)
|
||||||
|
}
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -23,12 +24,26 @@ type adapter struct {
|
|||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAdapter(fs fs.Filesystem, filePath string, logger log.Logger) *adapter {
|
func newAdapter(fs fs.Filesystem, filePath string, logger log.Logger) (*adapter, error) {
|
||||||
return &adapter{
|
a := &adapter{
|
||||||
fs: fs,
|
fs: fs,
|
||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.fs == nil {
|
||||||
|
return nil, fmt.Errorf("a filesystem has to be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.filePath) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid file path, file path cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.logger == nil {
|
||||||
|
a.logger = log.New("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adapter
|
// Adapter
|
||||||
@@ -36,10 +51,6 @@ func (a *adapter) LoadPolicy(model model.Model) error {
|
|||||||
a.lock.Lock()
|
a.lock.Lock()
|
||||||
defer a.lock.Unlock()
|
defer a.lock.Unlock()
|
||||||
|
|
||||||
if a.filePath == "" {
|
|
||||||
return fmt.Errorf("invalid file path, file path cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.loadPolicyFile(model)
|
return a.loadPolicyFile(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +80,7 @@ func (a *adapter) loadPolicyFile(model model.Model) error {
|
|||||||
rule[1] = "role:" + name
|
rule[1] = "role:" + name
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
rule[3] = role.Resource
|
rule[3] = role.Resource
|
||||||
rule[4] = role.Actions
|
rule[4] = formatActions(role.Actions)
|
||||||
|
|
||||||
if err := a.importPolicy(model, rule[0:5]); err != nil {
|
if err := a.importPolicy(model, rule[0:5]); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -80,7 +91,7 @@ func (a *adapter) loadPolicyFile(model model.Model) error {
|
|||||||
for _, policy := range group.Policies {
|
for _, policy := range group.Policies {
|
||||||
rule[1] = policy.Username
|
rule[1] = policy.Username
|
||||||
rule[3] = policy.Resource
|
rule[3] = policy.Resource
|
||||||
rule[4] = policy.Actions
|
rule[4] = formatActions(policy.Actions)
|
||||||
|
|
||||||
if err := a.importPolicy(model, rule[0:5]); err != nil {
|
if err := a.importPolicy(model, rule[0:5]); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -138,10 +149,6 @@ func (a *adapter) SavePolicy(model model.Model) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *adapter) savePolicyFile() error {
|
func (a *adapter) savePolicyFile() error {
|
||||||
if a.filePath == "" {
|
|
||||||
return fmt.Errorf("invalid file path, file path cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
jsondata, err := json.MarshalIndent(a.groups, "", " ")
|
jsondata, err := json.MarshalIndent(a.groups, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -201,7 +208,7 @@ func (a *adapter) addPolicy(ptype string, rule []string) error {
|
|||||||
username = rule[0]
|
username = rule[0]
|
||||||
domain = rule[1]
|
domain = rule[1]
|
||||||
resource = rule[2]
|
resource = rule[2]
|
||||||
actions = rule[3]
|
actions = formatActions(rule[3])
|
||||||
|
|
||||||
a.logger.Debug().WithFields(log.Fields{
|
a.logger.Debug().WithFields(log.Fields{
|
||||||
"username": username,
|
"username": username,
|
||||||
@@ -227,16 +234,20 @@ func (a *adapter) addPolicy(ptype string, rule []string) error {
|
|||||||
for i := range a.groups {
|
for i := range a.groups {
|
||||||
if a.groups[i].Name == domain {
|
if a.groups[i].Name == domain {
|
||||||
group = &a.groups[i]
|
group = &a.groups[i]
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if group == nil {
|
if group == nil {
|
||||||
g := Group{
|
g := Group{
|
||||||
Name: domain,
|
Name: domain,
|
||||||
|
Roles: map[string][]Role{},
|
||||||
|
UserRoles: []MapUserRole{},
|
||||||
|
Policies: []GroupPolicy{},
|
||||||
}
|
}
|
||||||
|
|
||||||
a.groups = append(a.groups, g)
|
a.groups = append(a.groups, g)
|
||||||
group = &g
|
group = &a.groups[len(a.groups)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if ptype == "p" {
|
if ptype == "p" {
|
||||||
@@ -251,8 +262,8 @@ func (a *adapter) addPolicy(ptype string, rule []string) error {
|
|||||||
Actions: actions,
|
Actions: actions,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
group.Policies = append(group.Policies, Policy{
|
group.Policies = append(group.Policies, GroupPolicy{
|
||||||
Username: rule[0],
|
Username: username,
|
||||||
Role: Role{
|
Role: Role{
|
||||||
Resource: resource,
|
Resource: resource,
|
||||||
Actions: actions,
|
Actions: actions,
|
||||||
@@ -284,7 +295,7 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) {
|
|||||||
username = rule[0]
|
username = rule[0]
|
||||||
domain = rule[1]
|
domain = rule[1]
|
||||||
resource = rule[2]
|
resource = rule[2]
|
||||||
actions = rule[3]
|
actions = formatActions(rule[3])
|
||||||
} else if ptype == "g" {
|
} else if ptype == "g" {
|
||||||
username = rule[0]
|
username = rule[0]
|
||||||
role = rule[1]
|
role = rule[1]
|
||||||
@@ -294,9 +305,9 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var group *Group = nil
|
var group *Group = nil
|
||||||
for _, g := range a.groups {
|
for i := range a.groups {
|
||||||
if g.Name == domain {
|
if a.groups[i].Name == domain {
|
||||||
group = &g
|
group = &a.groups[i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,13 +332,13 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
if role.Resource == resource && role.Actions == actions {
|
if role.Resource == resource && formatActions(role.Actions) == actions {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, p := range group.Policies {
|
for _, p := range group.Policies {
|
||||||
if p.Username == username && p.Resource == resource && p.Actions == actions {
|
if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,7 +404,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
|
|||||||
username = rule[0]
|
username = rule[0]
|
||||||
domain = rule[1]
|
domain = rule[1]
|
||||||
resource = rule[2]
|
resource = rule[2]
|
||||||
actions = rule[3]
|
actions = formatActions(rule[3])
|
||||||
|
|
||||||
a.logger.Debug().WithFields(log.Fields{
|
a.logger.Debug().WithFields(log.Fields{
|
||||||
"username": username,
|
"username": username,
|
||||||
@@ -419,6 +430,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
|
|||||||
for i := range a.groups {
|
for i := range a.groups {
|
||||||
if a.groups[i].Name == domain {
|
if a.groups[i].Name == domain {
|
||||||
group = &a.groups[i]
|
group = &a.groups[i]
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +447,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
|
|||||||
newRoles := []Role{}
|
newRoles := []Role{}
|
||||||
|
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
if role.Resource == resource && role.Actions == actions {
|
if role.Resource == resource && formatActions(role.Actions) == actions {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,10 +456,10 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
|
|||||||
|
|
||||||
group.Roles[username] = newRoles
|
group.Roles[username] = newRoles
|
||||||
} else {
|
} else {
|
||||||
policies := []Policy{}
|
policies := []GroupPolicy{}
|
||||||
|
|
||||||
for _, p := range group.Policies {
|
for _, p := range group.Policies {
|
||||||
if p.Username == username && p.Resource == resource && p.Actions == actions {
|
if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,6 +484,21 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
|
|||||||
group.UserRoles = users
|
group.UserRoles = users
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the group if there are no rules and policies
|
||||||
|
if len(group.Roles) == 0 && len(group.UserRoles) == 0 && len(group.Policies) == 0 {
|
||||||
|
groups := []Group{}
|
||||||
|
|
||||||
|
for _, g := range a.groups {
|
||||||
|
if g.Name == group.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
groups = append(groups, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.groups = groups
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,7 +525,7 @@ type Group struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Roles map[string][]Role `json:"roles"`
|
Roles map[string][]Role `json:"roles"`
|
||||||
UserRoles []MapUserRole `json:"userroles"`
|
UserRoles []MapUserRole `json:"userroles"`
|
||||||
Policies []Policy `json:"policies"`
|
Policies []GroupPolicy `json:"policies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Role struct {
|
type Role struct {
|
||||||
@@ -511,7 +538,15 @@ type MapUserRole struct {
|
|||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Policy struct {
|
type GroupPolicy struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Role
|
Role
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatActions(actions string) string {
|
||||||
|
a := strings.Split(actions, "|")
|
||||||
|
|
||||||
|
sort.Strings(a)
|
||||||
|
|
||||||
|
return strings.Join(a, "|")
|
||||||
|
}
|
||||||
|
87
iam/adapter_test.go
Normal file
87
iam/adapter_test.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package iam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/io/fs"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddPolicy(t *testing.T) {
|
||||||
|
memfs, err := fs.NewMemFilesystem(fs.MemConfig{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
a, err := newAdapter(memfs, "/policy.json", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = a.AddPolicy("p", "p", []string{"foobar", "group", "resource", "action"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(a.groups))
|
||||||
|
|
||||||
|
data, err := memfs.ReadFile("/policy.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
g := []Group{}
|
||||||
|
err = json.Unmarshal(data, &g)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "group", g[0].Name)
|
||||||
|
require.Equal(t, 1, len(g[0].Policies))
|
||||||
|
require.Equal(t, GroupPolicy{
|
||||||
|
Username: "foobar",
|
||||||
|
Role: Role{
|
||||||
|
Resource: "resource",
|
||||||
|
Actions: "action",
|
||||||
|
},
|
||||||
|
}, g[0].Policies[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatActions(t *testing.T) {
|
||||||
|
data := [][]string{
|
||||||
|
{"a|b|c", "a|b|c"},
|
||||||
|
{"b|c|a", "a|b|c"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range data {
|
||||||
|
require.Equal(t, d[1], formatActions(d[0]), d[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemovePolicy(t *testing.T) {
|
||||||
|
memfs, err := fs.NewMemFilesystem(fs.MemConfig{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
a, err := newAdapter(memfs, "/policy.json", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = a.AddPolicies("p", "p", [][]string{
|
||||||
|
{"foobar1", "group", "resource1", "action1"},
|
||||||
|
{"foobar2", "group", "resource2", "action2"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(a.groups))
|
||||||
|
require.Equal(t, 2, len(a.groups[0].Policies))
|
||||||
|
|
||||||
|
err = a.RemovePolicy("p", "p", []string{"foobar1", "group", "resource1", "action1"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(a.groups))
|
||||||
|
require.Equal(t, 1, len(a.groups[0].Policies))
|
||||||
|
|
||||||
|
err = a.RemovePolicy("p", "p", []string{"foobar2", "group", "resource2", "action2"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(a.groups))
|
||||||
|
|
||||||
|
data, err := memfs.ReadFile("/policy.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
g := []Group{}
|
||||||
|
err = json.Unmarshal(data, &g)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(g))
|
||||||
|
}
|
26
iam/fixtures/policy.json
Normal file
26
iam/fixtures/policy.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "$none",
|
||||||
|
"roles": {},
|
||||||
|
"userroles": [],
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"username": "ingo",
|
||||||
|
"resource": "rtmp:/bla-*",
|
||||||
|
"actions": "play|publish"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "igelcamp",
|
||||||
|
"roles": null,
|
||||||
|
"userroles": null,
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"username": "ingo",
|
||||||
|
"resource": "rtmp:/igelcamp/**",
|
||||||
|
"actions": "publish"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
68
iam/iam.go
68
iam/iam.go
@@ -9,21 +9,23 @@ type IAM interface {
|
|||||||
Enforce(user, domain, resource, action string) bool
|
Enforce(user, domain, resource, action string) bool
|
||||||
HasDomain(domain string) bool
|
HasDomain(domain string) bool
|
||||||
|
|
||||||
AddPolicy(username, domain, resource, actions string) bool
|
AddPolicy(username, domain, resource string, actions []string) bool
|
||||||
RemovePolicy(username, domain, resource, actions string) bool
|
RemovePolicy(username, domain, resource string, actions []string) bool
|
||||||
|
|
||||||
ListPolicies(username, domain, resource, actions string) [][]string
|
ListPolicies(username, domain, resource string, actions []string) []Policy
|
||||||
|
|
||||||
Validators() []string
|
Validators() []string
|
||||||
|
|
||||||
CreateIdentity(u User) error
|
CreateIdentity(u User) error
|
||||||
|
GetIdentity(name string) (User, error)
|
||||||
UpdateIdentity(name string, u User) error
|
UpdateIdentity(name string, u User) error
|
||||||
DeleteIdentity(name string) error
|
DeleteIdentity(name string) error
|
||||||
ListIdentities() []User
|
ListIdentities() []User
|
||||||
|
SaveIdentities() error
|
||||||
|
|
||||||
GetIdentity(name string) (IdentityVerifier, error)
|
GetVerifier(name string) (IdentityVerifier, error)
|
||||||
GetIdentityByAuth0(name string) (IdentityVerifier, error)
|
GetVerfierFromAuth0(name string) (IdentityVerifier, error)
|
||||||
GetDefaultIdentity() (IdentityVerifier, error)
|
GetDefaultVerifier() (IdentityVerifier, error)
|
||||||
|
|
||||||
CreateJWT(name string) (string, string, error)
|
CreateJWT(name string) (string, string, error)
|
||||||
|
|
||||||
@@ -85,17 +87,25 @@ func (i *iam) Close() {
|
|||||||
i.am = nil
|
i.am = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) Enforce(user, domain, resource, action string) bool {
|
func (i *iam) Enforce(name, domain, resource, action string) bool {
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = "$anon"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domain) == 0 {
|
||||||
|
domain = "$none"
|
||||||
|
}
|
||||||
|
|
||||||
superuser := false
|
superuser := false
|
||||||
|
|
||||||
if identity, err := i.im.GetVerifier(user); err == nil {
|
if identity, err := i.im.GetVerifier(name); err == nil {
|
||||||
if identity.IsSuperuser() {
|
if identity.IsSuperuser() {
|
||||||
superuser = true
|
superuser = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
l := i.logger.Debug().WithFields(log.Fields{
|
l := i.logger.Debug().WithFields(log.Fields{
|
||||||
"subject": user,
|
"subject": name,
|
||||||
"domain": domain,
|
"domain": domain,
|
||||||
"resource": resource,
|
"resource": resource,
|
||||||
"action": action,
|
"action": action,
|
||||||
@@ -103,10 +113,10 @@ func (i *iam) Enforce(user, domain, resource, action string) bool {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if superuser {
|
if superuser {
|
||||||
user = "$superuser"
|
name = "$superuser"
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, rule := i.am.Enforce(user, domain, resource, action)
|
ok, rule := i.am.Enforce(name, domain, resource, action)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
l.Log("no match")
|
l.Log("no match")
|
||||||
@@ -121,6 +131,10 @@ func (i *iam) CreateIdentity(u User) error {
|
|||||||
return i.im.Create(u)
|
return i.im.Create(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *iam) GetIdentity(name string) (User, error) {
|
||||||
|
return i.im.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
func (i *iam) UpdateIdentity(name string, u User) error {
|
func (i *iam) UpdateIdentity(name string, u User) error {
|
||||||
return i.im.Update(name, u)
|
return i.im.Update(name, u)
|
||||||
}
|
}
|
||||||
@@ -133,15 +147,19 @@ func (i *iam) ListIdentities() []User {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) GetIdentity(name string) (IdentityVerifier, error) {
|
func (i *iam) SaveIdentities() error {
|
||||||
|
return i.im.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iam) GetVerifier(name string) (IdentityVerifier, error) {
|
||||||
return i.im.GetVerifier(name)
|
return i.im.GetVerifier(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) GetIdentityByAuth0(name string) (IdentityVerifier, error) {
|
func (i *iam) GetVerfierFromAuth0(name string) (IdentityVerifier, error) {
|
||||||
return i.im.GetVerifierByAuth0(name)
|
return i.im.GetVerifierFromAuth0(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) GetDefaultIdentity() (IdentityVerifier, error) {
|
func (i *iam) GetDefaultVerifier() (IdentityVerifier, error) {
|
||||||
return i.im.GetDefaultVerifier()
|
return i.im.GetDefaultVerifier()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,14 +175,22 @@ func (i *iam) Validators() []string {
|
|||||||
return i.im.Validators()
|
return i.im.Validators()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) AddPolicy(username, domain, resource, actions string) bool {
|
func (i *iam) AddPolicy(name, domain, resource string, actions []string) bool {
|
||||||
return i.am.AddPolicy(username, domain, resource, actions)
|
if len(name) == 0 {
|
||||||
|
name = "$anon"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domain) == 0 {
|
||||||
|
domain = "$none"
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.am.AddPolicy(name, domain, resource, actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) RemovePolicy(username, domain, resource, actions string) bool {
|
func (i *iam) RemovePolicy(name, domain, resource string, actions []string) bool {
|
||||||
return i.am.RemovePolicy(username, domain, resource, actions)
|
return i.am.RemovePolicy(name, domain, resource, actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) ListPolicies(username, domain, resource, actions string) [][]string {
|
func (i *iam) ListPolicies(name, domain, resource string, actions []string) []Policy {
|
||||||
return i.am.ListPolicies(username, domain, resource, actions)
|
return i.am.ListPolicies(name, domain, resource, actions)
|
||||||
}
|
}
|
||||||
|
@@ -378,7 +378,7 @@ type IdentityManager interface {
|
|||||||
|
|
||||||
Get(name string) (User, error)
|
Get(name string) (User, error)
|
||||||
GetVerifier(name string) (IdentityVerifier, error)
|
GetVerifier(name string) (IdentityVerifier, error)
|
||||||
GetVerifierByAuth0(name string) (IdentityVerifier, error)
|
GetVerifierFromAuth0(name string) (IdentityVerifier, error)
|
||||||
GetDefaultVerifier() (IdentityVerifier, error)
|
GetDefaultVerifier() (IdentityVerifier, error)
|
||||||
|
|
||||||
Validators() []string
|
Validators() []string
|
||||||
@@ -697,7 +697,7 @@ func (im *identityManager) GetVerifier(name string) (IdentityVerifier, error) {
|
|||||||
return im.getIdentity(name)
|
return im.getIdentity(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *identityManager) GetVerifierByAuth0(name string) (IdentityVerifier, error) {
|
func (im *identityManager) GetVerifierFromAuth0(name string) (IdentityVerifier, error) {
|
||||||
im.lock.RLock()
|
im.lock.RLock()
|
||||||
defer im.lock.RUnlock()
|
defer im.lock.RUnlock()
|
||||||
|
|
||||||
@@ -841,9 +841,9 @@ func (im *identityManager) CreateJWT(name string) (string, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Auth0Tenant struct {
|
type Auth0Tenant struct {
|
||||||
Domain string
|
Domain string `json:"domain"`
|
||||||
Audience string
|
Audience string `json:"audience"`
|
||||||
ClientID string
|
ClientID string `json:"client_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Auth0Tenant) key() string {
|
func (t *Auth0Tenant) key() string {
|
||||||
|
@@ -353,11 +353,11 @@ func TestCreateUserAuth0(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
identity, err := im.GetVerifierByAuth0("foobaz")
|
identity, err := im.GetVerifierFromAuth0("foobaz")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, identity)
|
require.Nil(t, identity)
|
||||||
|
|
||||||
identity, err = im.GetVerifierByAuth0("auth0|123456")
|
identity, err = im.GetVerifierFromAuth0("auth0|123456")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, identity)
|
require.NotNil(t, identity)
|
||||||
|
|
||||||
@@ -553,7 +553,7 @@ func TestUpdateUserAuth0(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
identity, err := im.GetVerifierByAuth0("auth0|123456")
|
identity, err := im.GetVerifierFromAuth0("auth0|123456")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, identity)
|
require.NotNil(t, identity)
|
||||||
|
|
||||||
@@ -569,7 +569,7 @@ func TestUpdateUserAuth0(t *testing.T) {
|
|||||||
err = im.Update("foobaz", user)
|
err = im.Update("foobaz", user)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
identity, err = im.GetVerifierByAuth0("auth0|123456")
|
identity, err = im.GetVerifierFromAuth0("auth0|123456")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, identity)
|
require.NotNil(t, identity)
|
||||||
|
|
||||||
|
1
io/fs/fixtures/a.txt
Normal file
1
io/fs/fixtures/a.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
qwertz
|
1
io/fs/fixtures/b.txt
Normal file
1
io/fs/fixtures/b.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
qwertz
|
@@ -156,6 +156,8 @@ func NewMemFilesystemFromDir(dir string, config MemConfig) (Filesystem, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
|
||||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -181,7 +183,7 @@ func NewMemFilesystemFromDir(dir string, config MemConfig) (Filesystem, error) {
|
|||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
_, _, err = mem.WriteFileReader(path, file)
|
_, _, err = mem.WriteFileReader(strings.TrimPrefix(path, dir), file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't copy %s", path)
|
return fmt.Errorf("can't copy %s", path)
|
||||||
}
|
}
|
||||||
|
@@ -7,24 +7,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMemFromDir(t *testing.T) {
|
func TestMemFromDir(t *testing.T) {
|
||||||
mem, err := NewMemFilesystemFromDir(".", MemConfig{})
|
mem, err := NewMemFilesystemFromDir("./fixtures", MemConfig{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
names := []string{}
|
names := []string{}
|
||||||
for _, f := range mem.List("/", "/*.go") {
|
for _, f := range mem.List("/", "") {
|
||||||
names = append(names, f.Name())
|
names = append(names, f.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
require.ElementsMatch(t, []string{
|
require.ElementsMatch(t, []string{
|
||||||
"/disk.go",
|
"/a.txt",
|
||||||
"/fs_test.go",
|
"/b.txt",
|
||||||
"/fs.go",
|
|
||||||
"/mem_test.go",
|
|
||||||
"/mem.go",
|
|
||||||
"/readonly_test.go",
|
|
||||||
"/readonly.go",
|
|
||||||
"/s3.go",
|
|
||||||
"/sized_test.go",
|
|
||||||
"/sized.go",
|
|
||||||
}, names)
|
}, names)
|
||||||
}
|
}
|
||||||
|
@@ -424,7 +424,7 @@ func (r *restream) enforce(name, group, processid, action string) bool {
|
|||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
// This is for backwards compatibility. Existing processes don't have an owner.
|
// This is for backwards compatibility. Existing processes don't have an owner.
|
||||||
// All processes that will be added later will have an owner ($anon, ...).
|
// All processes that will be added later will have an owner ($anon, ...).
|
||||||
identity, err := r.iam.GetDefaultIdentity()
|
identity, err := r.iam.GetDefaultVerifier()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
name = "$anon"
|
name = "$anon"
|
||||||
} else {
|
} else {
|
||||||
@@ -914,7 +914,7 @@ func (r *restream) resolveAddress(tasks map[string]*task, id, address string) (s
|
|||||||
return address, fmt.Errorf("unknown process '%s' in group '%s' (%s)", matches["id"], matches["group"], address)
|
return address, fmt.Errorf("unknown process '%s' in group '%s' (%s)", matches["id"], matches["group"], address)
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, _ := r.iam.GetIdentity(t.config.Owner)
|
identity, _ := r.iam.GetVerifier(t.config.Owner)
|
||||||
|
|
||||||
teeOptions := regexp.MustCompile(`^\[[^\]]*\]`)
|
teeOptions := regexp.MustCompile(`^\[[^\]]*\]`)
|
||||||
|
|
||||||
|
@@ -51,7 +51,7 @@ func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmp
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
iam.AddPolicy("$anon", "$none", "process:*", "CREATE|GET|DELETE|UPDATE|COMMAND|PROBE|METADATA|PLAYOUT")
|
iam.AddPolicy("$anon", "$none", "process:*", []string{"CREATE", "GET", "DELETE", "UPDATE", "COMMAND", "PROBE", "METADATA", "PLAYOUT"})
|
||||||
|
|
||||||
rewriter, err := rewrite.New(rewrite.Config{})
|
rewriter, err := rewrite.New(rewrite.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -3,7 +3,6 @@ package rtmp
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -94,11 +93,11 @@ type channel struct {
|
|||||||
isProxy bool
|
isProxy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newChannel(conn connection, u *url.URL, reference string, remote net.Addr, streams []av.CodecData, isProxy bool, collector session.Collector) *channel {
|
func newChannel(conn connection, playpath, reference string, remote net.Addr, streams []av.CodecData, isProxy bool, collector session.Collector) *channel {
|
||||||
ch := &channel{
|
ch := &channel{
|
||||||
path: u.Path,
|
path: playpath,
|
||||||
reference: reference,
|
reference: reference,
|
||||||
publisher: newClient(conn, u.Path, collector),
|
publisher: newClient(conn, playpath, collector),
|
||||||
subscriber: make(map[string]*client),
|
subscriber: make(map[string]*client),
|
||||||
collector: collector,
|
collector: collector,
|
||||||
streams: streams,
|
streams: streams,
|
||||||
|
115
rtmp/rtmp.go
115
rtmp/rtmp.go
@@ -106,7 +106,7 @@ func New(config Config) (Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s := &server{
|
s := &server{
|
||||||
app: config.App,
|
app: filepath.Join("/", config.App),
|
||||||
token: config.Token,
|
token: config.Token,
|
||||||
logger: config.Logger,
|
logger: config.Logger,
|
||||||
collector: config.Collector,
|
collector: config.Collector,
|
||||||
@@ -184,19 +184,19 @@ func (s *server) Channels() []string {
|
|||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) log(who, action, path, message string, client net.Addr) {
|
func (s *server) log(who, what, action, path, message string, client net.Addr) {
|
||||||
s.logger.Info().WithFields(log.Fields{
|
s.logger.Info().WithFields(log.Fields{
|
||||||
"who": who,
|
"who": who,
|
||||||
|
"what": what,
|
||||||
"action": action,
|
"action": action,
|
||||||
"path": path,
|
"path": path,
|
||||||
"client": client.String(),
|
"client": client.String(),
|
||||||
}).Log(message)
|
}).Log(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetToken returns the path and the token found in the URL. If the token
|
// GetToken returns the path without the token and the token found in the URL. If the token
|
||||||
// was part of the path, the token is removed from the path. The token in
|
// was part of the path, the token is removed from the path. The token in the query string
|
||||||
// the query string takes precedence. The token in the path is assumed to
|
// takes precedence. The token in the path is assumed to be the last path element.
|
||||||
// be the last path element.
|
|
||||||
func GetToken(u *url.URL) (string, string) {
|
func GetToken(u *url.URL) (string, string) {
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
if q.Has("token") {
|
if q.Has("token") {
|
||||||
@@ -204,15 +204,34 @@ func GetToken(u *url.URL) (string, string) {
|
|||||||
return u.Path, q.Get("token")
|
return u.Path, q.Get("token")
|
||||||
}
|
}
|
||||||
|
|
||||||
pathElements := strings.Split(u.EscapedPath(), "/")
|
pathElements := splitPath(u.EscapedPath())
|
||||||
nPathElements := len(pathElements)
|
nPathElements := len(pathElements)
|
||||||
|
|
||||||
if nPathElements == 0 {
|
if nPathElements <= 1 {
|
||||||
return u.Path, ""
|
return u.Path, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the path without the token
|
// Return the path without the token
|
||||||
return strings.Join(pathElements[:nPathElements-1], "/"), pathElements[nPathElements-1]
|
return "/" + strings.Join(pathElements[:nPathElements-1], "/"), pathElements[nPathElements-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitPath(path string) []string {
|
||||||
|
pathElements := strings.Split(filepath.Clean(path), "/")
|
||||||
|
|
||||||
|
if len(pathElements) == 0 {
|
||||||
|
return pathElements
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pathElements[0]) == 0 {
|
||||||
|
pathElements = pathElements[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathElements
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePathPrefix(path, prefix string) (string, string) {
|
||||||
|
prefix = filepath.Join("/", prefix)
|
||||||
|
return filepath.Join("/", strings.TrimPrefix(path, prefix+"/")), prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePlay is called when a RTMP client wants to play a stream
|
// handlePlay is called when a RTMP client wants to play a stream
|
||||||
@@ -220,20 +239,22 @@ func (s *server) handlePlay(conn *rtmp.Conn) {
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
remote := conn.NetConn().RemoteAddr()
|
remote := conn.NetConn().RemoteAddr()
|
||||||
playPath, token := GetToken(conn.URL)
|
playpath, token := GetToken(conn.URL)
|
||||||
|
|
||||||
|
playpath, _ = removePathPrefix(playpath, s.app)
|
||||||
|
|
||||||
identity, err := s.findIdentityFromStreamKey(token)
|
identity, err := s.findIdentityFromStreamKey(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Debug().WithError(err).Log("invalid streamkey")
|
s.logger.Debug().WithError(err).Log("invalid streamkey")
|
||||||
s.log("PLAY", "FORBIDDEN", playPath, "invalid streamkey ("+token+")", remote)
|
s.log(identity, "PLAY", "FORBIDDEN", playpath, "invalid streamkey ("+token+")", remote)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domain := s.findDomainFromPlaypath(playPath)
|
domain := s.findDomainFromPlaypath(playpath)
|
||||||
resource := "rtmp:" + playPath
|
resource := "rtmp:" + playpath
|
||||||
|
|
||||||
if !s.iam.Enforce(identity, domain, resource, "PLAY") {
|
if !s.iam.Enforce(identity, domain, resource, "PLAY") {
|
||||||
s.log("PLAY", "FORBIDDEN", playPath, "access denied", remote)
|
s.log(identity, "PLAY", "FORBIDDEN", playpath, "access denied", remote)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,14 +279,14 @@ func (s *server) handlePlay(conn *rtmp.Conn) {
|
|||||||
|
|
||||||
// Look for the stream
|
// Look for the stream
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
ch := s.channels[playPath]
|
ch := s.channels[playpath]
|
||||||
s.lock.RUnlock()
|
s.lock.RUnlock()
|
||||||
|
|
||||||
if ch != nil {
|
if ch != nil {
|
||||||
// Send the metadata to the client
|
// Send the metadata to the client
|
||||||
conn.WriteHeader(ch.streams)
|
conn.WriteHeader(ch.streams)
|
||||||
|
|
||||||
s.log("PLAY", "START", conn.URL.Path, "", remote)
|
s.log(identity, "PLAY", "START", conn.URL.Path, "", remote)
|
||||||
|
|
||||||
// Get a cursor and apply filters
|
// Get a cursor and apply filters
|
||||||
cursor := ch.queue.Oldest()
|
cursor := ch.queue.Oldest()
|
||||||
@@ -292,9 +313,9 @@ func (s *server) handlePlay(conn *rtmp.Conn) {
|
|||||||
|
|
||||||
ch.RemoveSubscriber(id)
|
ch.RemoveSubscriber(id)
|
||||||
|
|
||||||
s.log("PLAY", "STOP", playPath, "", remote)
|
s.log(identity, "PLAY", "STOP", playpath, "", remote)
|
||||||
} else {
|
} else {
|
||||||
s.log("PLAY", "NOTFOUND", playPath, "", remote)
|
s.log(identity, "PLAY", "NOTFOUND", playpath, "", remote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,52 +324,54 @@ func (s *server) handlePublish(conn *rtmp.Conn) {
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
remote := conn.NetConn().RemoteAddr()
|
remote := conn.NetConn().RemoteAddr()
|
||||||
playPath, token := GetToken(conn.URL)
|
playpath, token := GetToken(conn.URL)
|
||||||
|
|
||||||
// Check the app patch
|
playpath, app := removePathPrefix(playpath, s.app)
|
||||||
if !strings.HasPrefix(playPath, s.app) {
|
|
||||||
s.log("PUBLISH", "FORBIDDEN", conn.URL.Path, "invalid app", remote)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
identity, err := s.findIdentityFromStreamKey(token)
|
identity, err := s.findIdentityFromStreamKey(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Debug().WithError(err).Log("invalid streamkey")
|
s.logger.Debug().WithError(err).Log("invalid streamkey")
|
||||||
s.log("PUBLISH", "FORBIDDEN", playPath, "invalid streamkey ("+token+")", remote)
|
s.log(identity, "PUBLISH", "FORBIDDEN", playpath, "invalid streamkey ("+token+")", remote)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domain := s.findDomainFromPlaypath(playPath)
|
// Check the app patch
|
||||||
resource := "rtmp:" + playPath
|
if app != s.app {
|
||||||
|
s.log(identity, "PUBLISH", "FORBIDDEN", playpath, "invalid app", remote)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := s.findDomainFromPlaypath(playpath)
|
||||||
|
resource := "rtmp:" + playpath
|
||||||
|
|
||||||
if !s.iam.Enforce(identity, domain, resource, "PUBLISH") {
|
if !s.iam.Enforce(identity, domain, resource, "PUBLISH") {
|
||||||
s.log("PUBLISH", "FORBIDDEN", playPath, "access denied", remote)
|
s.log(identity, "PUBLISH", "FORBIDDEN", playpath, "access denied", remote)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.publish(conn, conn.URL, remote, false)
|
err = s.publish(conn, playpath, remote, identity, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.WithField("path", conn.URL.Path).WithError(err).Log("")
|
s.logger.WithField("path", conn.URL.Path).WithError(err).Log("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) publish(src connection, u *url.URL, remote net.Addr, isProxy bool) error {
|
func (s *server) publish(src connection, playpath string, remote net.Addr, identity string, isProxy bool) error {
|
||||||
// Check the streams if it contains any valid/known streams
|
// Check the streams if it contains any valid/known streams
|
||||||
streams, _ := src.Streams()
|
streams, _ := src.Streams()
|
||||||
|
|
||||||
if len(streams) == 0 {
|
if len(streams) == 0 {
|
||||||
s.log("PUBLISH", "INVALID", u.Path, "no streams available", remote)
|
s.log(identity, "PUBLISH", "INVALID", playpath, "no streams available", remote)
|
||||||
return fmt.Errorf("no streams are available")
|
return fmt.Errorf("no streams are available")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
|
|
||||||
ch := s.channels[u.Path]
|
ch := s.channels[playpath]
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
reference := strings.TrimPrefix(strings.TrimSuffix(u.Path, filepath.Ext(u.Path)), s.app+"/")
|
reference := strings.TrimPrefix(strings.TrimSuffix(playpath, filepath.Ext(playpath)), s.app+"/")
|
||||||
|
|
||||||
// Create a new channel
|
// Create a new channel
|
||||||
ch = newChannel(src, u, reference, remote, streams, isProxy, s.collector)
|
ch = newChannel(src, playpath, reference, remote, streams, isProxy, s.collector)
|
||||||
|
|
||||||
for _, stream := range streams {
|
for _, stream := range streams {
|
||||||
typ := stream.Type()
|
typ := stream.Type()
|
||||||
@@ -361,7 +384,7 @@ func (s *server) publish(src connection, u *url.URL, remote net.Addr, isProxy bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.channels[u.Path] = ch
|
s.channels[playpath] = ch
|
||||||
} else {
|
} else {
|
||||||
ch = nil
|
ch = nil
|
||||||
}
|
}
|
||||||
@@ -369,26 +392,26 @@ func (s *server) publish(src connection, u *url.URL, remote net.Addr, isProxy bo
|
|||||||
s.lock.Unlock()
|
s.lock.Unlock()
|
||||||
|
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
s.log("PUBLISH", "CONFLICT", u.Path, "already publishing", remote)
|
s.log(identity, "PUBLISH", "CONFLICT", playpath, "already publishing", remote)
|
||||||
return fmt.Errorf("already publishing")
|
return fmt.Errorf("already publishing")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log("PUBLISH", "START", u.Path, "", remote)
|
s.log(identity, "PUBLISH", "START", playpath, "", remote)
|
||||||
|
|
||||||
for _, stream := range streams {
|
for _, stream := range streams {
|
||||||
s.log("PUBLISH", "STREAM", u.Path, stream.Type().String(), remote)
|
s.log(identity, "PUBLISH", "STREAM", playpath, stream.Type().String(), remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ingest the data, blocks until done
|
// Ingest the data, blocks until done
|
||||||
avutil.CopyPackets(ch.queue, src)
|
avutil.CopyPackets(ch.queue, src)
|
||||||
|
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
delete(s.channels, u.Path)
|
delete(s.channels, playpath)
|
||||||
s.lock.Unlock()
|
s.lock.Unlock()
|
||||||
|
|
||||||
ch.Close()
|
ch.Close()
|
||||||
|
|
||||||
s.log("PUBLISH", "STOP", u.Path, "", remote)
|
s.log(identity, "PUBLISH", "STOP", playpath, "", remote)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -405,10 +428,10 @@ func (s *server) findIdentityFromStreamKey(key string) (string, error) {
|
|||||||
|
|
||||||
elements := strings.Split(key, ":")
|
elements := strings.Split(key, ":")
|
||||||
if len(elements) == 1 {
|
if len(elements) == 1 {
|
||||||
identity, err = s.iam.GetDefaultIdentity()
|
identity, err = s.iam.GetDefaultVerifier()
|
||||||
token = elements[0]
|
token = elements[0]
|
||||||
} else {
|
} else {
|
||||||
identity, err = s.iam.GetIdentity(elements[0])
|
identity, err = s.iam.GetVerifier(elements[0])
|
||||||
token = elements[1]
|
token = elements[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,10 +446,12 @@ func (s *server) findIdentityFromStreamKey(key string) (string, error) {
|
|||||||
return identity.Name(), nil
|
return identity.Name(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findDomainFromPlaypath finds the domain in the path. The domain is
|
||||||
|
// the first path element. If there's only one path element, it is not
|
||||||
|
// considered the domain. It is assumed that the app is not part of
|
||||||
|
// the provided path.
|
||||||
func (s *server) findDomainFromPlaypath(path string) string {
|
func (s *server) findDomainFromPlaypath(path string) string {
|
||||||
path = strings.TrimPrefix(path, filepath.Join(s.app, "/"))
|
elements := splitPath(path)
|
||||||
|
|
||||||
elements := strings.Split(path, "/")
|
|
||||||
if len(elements) == 1 {
|
if len(elements) == 1 {
|
||||||
return "$none"
|
return "$none"
|
||||||
}
|
}
|
||||||
|
@@ -24,3 +24,31 @@ func TestToken(t *testing.T) {
|
|||||||
require.Equal(t, d[2], token, "url=%s", u.String())
|
require.Equal(t, d[2], token, "url=%s", u.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSplitPath(t *testing.T) {
|
||||||
|
data := map[string][]string{
|
||||||
|
"/foo/bar": {"foo", "bar"},
|
||||||
|
"foo/bar": {"foo", "bar"},
|
||||||
|
"/foo/bar/": {"foo", "bar"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for path, split := range data {
|
||||||
|
elms := splitPath(path)
|
||||||
|
|
||||||
|
require.ElementsMatch(t, split, elms, "%s", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemovePathPrefix(t *testing.T) {
|
||||||
|
data := [][]string{
|
||||||
|
{"/foo/bar", "/foo", "/bar"},
|
||||||
|
{"/foo/bar", "/fo", "/foo/bar"},
|
||||||
|
{"/foo/bar/abc", "/foo/bar", "/abc"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range data {
|
||||||
|
x, _ := removePathPrefix(d[0], d[1])
|
||||||
|
|
||||||
|
require.Equal(t, d[2], x, "path=%s prefix=%s", d[0], d[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -387,10 +387,10 @@ func (s *server) findIdentityFromToken(key string) (string, error) {
|
|||||||
|
|
||||||
elements := strings.Split(key, ":")
|
elements := strings.Split(key, ":")
|
||||||
if len(elements) == 1 {
|
if len(elements) == 1 {
|
||||||
identity, err = s.iam.GetDefaultIdentity()
|
identity, err = s.iam.GetDefaultVerifier()
|
||||||
token = elements[0]
|
token = elements[0]
|
||||||
} else {
|
} else {
|
||||||
identity, err = s.iam.GetIdentity(elements[0])
|
identity, err = s.iam.GetVerifier(elements[0])
|
||||||
token = elements[1]
|
token = elements[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user