Skip to content

Commit

Permalink
Alias feature (#686)
Browse files Browse the repository at this point in the history
* improve error handling in editor

* update git Dockerfile

* fix compose files

* add api support for linked keys

* add management support for linked keys

* [editor] redirect link keys

* organize files

* add authoring support for linked keys

* [editor] display linked keys

* Added api and management unit tests

* rename 'link' key type to 'alias'

* added key aliases test

* combine dependents endpoints

* [editor] handle alias value type

* added more tests

* [editor] deleting a key should delete aliases

* [editor] move keyPath validation to NewKeyInput component

* [editor] Alert component can display custom components

* [editor] added addAlias action to selectedKey duck

* [editor] added validation and component data to Alert component

* [editor] NewKeyInput styles

* [editor] added option to add alias for key

* [e2e-ui] fix aliases tests

* [e2e-ui] added add alias test

* [editor] style review

* [authoring] fix delete single key

* [editor] added delete alias

* [e2e-ui] added delete alias test

* [e2e-integration] rename test key

* [smoke-tests] rename test keys

* [e2e-integration] added alias test

* code review

* bump versions
  • Loading branch information
nataly87s committed Dec 14, 2017
1 parent b00dafe commit e8f8ee9
Show file tree
Hide file tree
Showing 173 changed files with 1,790 additions and 1,471 deletions.
5 changes: 4 additions & 1 deletion core/Engine/Tweek.Engine.Core/Rules/Utils.cs
Expand Up @@ -9,10 +9,13 @@ namespace Tweek.Engine.Core.Rules
{
public static class Utils
{
public static IRuleParser ConstValueParser = new AnonymousParser(str =>
public static readonly IRuleParser ConstValueParser = new AnonymousParser(str =>
Prelude.map(JsonValue.From(JToken.Parse(str)),
(value) => new AnonymousRule(ctx => ConfigurationValue.New(value))));

public static readonly IRuleParser KeyAliasParser = new AnonymousParser(originalKey =>
new AnonymousRule(ctx => ctx($"keys.{originalKey}").Map(ConfigurationValue.New)));

public class AnonymousRule : IRule
{
private readonly Func<GetContextValue, Option<ConfigurationValue>> fn;
Expand Down
32 changes: 32 additions & 0 deletions core/Engine/Tweek.Engine.Tests/Core/KeyAliasParserTests.cs
@@ -0,0 +1,32 @@
using LanguageExt;
using Tweek.Engine.Core.Context;
using Xunit;
using static LanguageExt.Prelude;

namespace Tweek.Engine.Tests.Core
{
public class KeyAliasParserTests
{
[Fact]
public void ParseLink_GetLinkValue()
{
// Arrange
const string ORIGINAL_KEY = "some_key";
var parser = Engine.Core.Rules.Utils.KeyAliasParser;

string requestedContext = null;
var getContextValue = new GetContextValue(key =>
{
requestedContext = key;
return None;
});

// Act
var result = parser.Parse(ORIGINAL_KEY).GetValue(getContextValue);

// Assert
Assert.Equal(result, None);
Assert.Equal(requestedContext, $"keys.{ORIGINAL_KEY}");
}
}
}
2 changes: 0 additions & 2 deletions deployments/dev/docker-compose.e2e.yml
Expand Up @@ -4,7 +4,6 @@ services:
management:
environment:
- GIT_URL=ssh://git@git/tweek/tests
- GIT_SAMPLE_INTERVAL=500

editor:
environment:
Expand All @@ -20,7 +19,6 @@ services:

api:
environment:
- Rules__SampleIntervalInMs=500
- CorsPolicies__Keys__Origins=http://editor:3000,http://testorigin
- CorsPolicies__Keys__Methods=GET
- CorsPolicies__Keys__MaxPreflightAge=60
Expand Down
2 changes: 2 additions & 0 deletions deployments/dev/docker-compose.override.yml
Expand Up @@ -30,6 +30,7 @@ services:
- GIT_URL=ssh://git@git/tweek/repo
- GIT_PUBLIC_KEY_PATH=/run/secrets/tweek_ssh_public_key
- GIT_PRIVATE_KEY_PATH=/run/secrets/tweek_ssh_private_key
- GIT_SAMPLE_INTERVAL=500
- MINIO_ENDPOINT=minio:9000
- MINIO_ACCESS_KEY_PATH=/run/secrets/minio_access_key
- MINIO_SECRET_KEY_PATH=/run/secrets/minio_secret_key
Expand Down Expand Up @@ -58,6 +59,7 @@ services:
- Rules__Minio__SecretKeyPath=/run/secrets/minio_secret_key
- Rules__Nats__Endpoint=nats://nats:4222
- Rules__Management__Url=http://management:3000
- Rules__SampleIntervalInMs=500
- Rules__FailureDelayInMs=50
- Rules__VersionTimeoutInMs=2000
- UseAddon__Rules=MinioRules
Expand Down
62 changes: 27 additions & 35 deletions e2e/integration/spec/authoring-api/add-app.test.js
@@ -1,45 +1,37 @@
const chai = require('chai');
chai.should();
const {init:initClients} = require("../../utils/clients");
const delay = (duration)=> new Promise(resolve=>setTimeout(resolve,duration));
const { init: initClients } = require('../../utils/clients');

describe('authoring api', () => {
describe('authoring api - add app', () => {
let clients;
before(async () => {
clients = await initClients();
});

describe('/posts /apps/new', () => {
it('allow creating new app with keys-read permission', async () => {
const response = await clients.authoring.post('/api/apps?author.name=test&author.email=test@soluto.com')
.send({ name: 'my-app', permissions: ['keys-read'] })
.expect(200);

let {appId, appSecret} = response.body;

let appClient = await clients.authoring.with(client =>
client.set( {"x-client-id": appId, "x-client-secret": appSecret } )
.unset("Authorization")
);

await appClient.get('/api/keys/@integration_tests/some_key')
.expect(200)

await appClient.get('/api/keys')
.expect(403)
});

it('allow creating new app with invalid permission', async () => {
await clients.authoring.post('/api/apps?author.name=test&author.email=test@soluto.com')
.send({ name: 'my-app', permissions: ['admin'] })
.expect(400);

await clients.authoring.post('/api/apps?author.name=test&author.email=test@soluto.com')
.send({ name: 'my-app', permissions: ['my-permission'] })
.expect(400);

});
it('allow creating new app with keys-read permission', async () => {
const response = await clients.authoring
.post('/api/apps?author.name=test&author.email=test@soluto.com')
.send({ name: 'my-app', permissions: ['keys-read'] })
.expect(200);

let { appId, appSecret } = response.body;

let appClient = await clients.authoring.with(client =>
client.set({ 'x-client-id': appId, 'x-client-secret': appSecret }).unset('Authorization'),
);

await appClient.get('/api/keys/integration_tests/some_key').expect(200);

await appClient.get('/api/keys').expect(403);
});

it('allow creating new app with invalid permission', async () => {
await clients.authoring
.post('/api/apps?author.name=test&author.email=test@soluto.com')
.send({ name: 'my-app', permissions: ['admin'] })
.expect(400);

await clients.authoring
.post('/api/apps?author.name=test&author.email=test@soluto.com')
.send({ name: 'my-app', permissions: ['my-permission'] })
.expect(400);
});
});
206 changes: 103 additions & 103 deletions e2e/integration/spec/authoring-api/app-permissions.test.js
@@ -1,131 +1,131 @@
const { init: initClients } = require("../../utils/clients");
const permissions = ['keys-list', 'keys-read', 'keys-write', 'schemas-read', 'schemas-write', 'history', 'search', 'search-index', 'tags-read', 'tags-write'];
const { init: initClients } = require('../../utils/clients');
const permissions = [
'keys-list',
'keys-read',
'keys-write',
'schemas-read',
'schemas-write',
'history',
'search',
'search-index',
'tags-read',
'tags-write',
];
const { expect } = require('chai');

describe("app permissions test", () => {
let clients;
before(async () => {
clients = await initClients();
});
const cases = [{
name: "read_specific_key",
requirePermission: "keys-read",
action: async (client) => {
await client.get('/api/keys/@integration_tests/some_key')
.expect(200)
}
describe('authoring api - app permissions', () => {
let clients;
before(async () => {
clients = await initClients();
});
const cases = [
{
name: 'read_specific_key',
requirePermission: 'keys-read',
action: async client => {
await client.get('/api/keys/integration_tests/some_key').expect(200);
},
},
{
name: "read_specific_key",
requirePermission: "keys-read",
action: async (client) => {
await client.get('/api/key?keyPath=%40integration_tests%2Fsome_key')
.expect(200)
}
name: 'read_specific_key',
requirePermission: 'keys-read',
action: async client => {
await client.get('/api/key?keyPath=integration_tests%2Fsome_key').expect(200);
},
},
{
name: "read_manifests",
requirePermission: "keys-read",
action: async (client) => {
await client.get('/api/manifests/@integration_tests/some_key')
.expect(200)
}
name: 'read_manifests',
requirePermission: 'keys-read',
action: async client => {
await client.get('/api/manifests/integration_tests/some_key').expect(200);
},
},
{
name: "list_keys",
requirePermission: "keys-list",
action: async (client) => {
await client.get('/api/keys')
.expect(200)
}
name: 'list_keys',
requirePermission: 'keys-list',
action: async client => {
await client.get('/api/keys').expect(200);
},
},
{
name: "list_manifests",
requirePermission: "keys-list",
action: async (client) => {
await client.get('/api/manifests')
.expect(200)
}
name: 'list_manifests',
requirePermission: 'keys-list',
action: async client => {
await client.get('/api/manifests').expect(200);
},
},
{
name: "get_dependents",
requirePermission: "keys-read",
action: async (client) => {
await client.get('/api/dependents/@integration_tests/some_key')
.expect(200)
}
name: 'get_dependents',
requirePermission: 'keys-read',
action: async client => {
await client.get('/api/dependents/integration_tests/some_key').expect(200);
},
},
{
name: "get_dependents",
requirePermission: "keys-read",
action: async (client) => {
await client.get('/api/dependents/@integration_tests/some_key')
.expect(200)
}
name: 'get_dependents',
requirePermission: 'keys-read',
action: async client => {
await client.get('/api/dependents/integration_tests/some_key').expect(200);
},
},
{
name: "get_schemas",
requirePermission: "schemas_read",
action: async (client) => {
await client.get('/api/schemas')
.expect(200)
}
name: 'get_schemas',
requirePermission: 'schemas_read',
action: async client => {
await client.get('/api/schemas').expect(200);
},
},
{
name: "search",
requirePermission: "search",
action: async (client) => {
await client.get('/api/search')
.expect(200)
}
name: 'search',
requirePermission: 'search',
action: async client => {
await client.get('/api/search').expect(200);
},
},
{
name: "suggestions",
requirePermission: "search",
action: async (client) => {
await client.get('/api/suggestions')
.expect(200)
}
name: 'suggestions',
requirePermission: 'search',
action: async client => {
await client.get('/api/suggestions').expect(200);
},
},
{
name: "search-index",
requirePermission: "search-index",
action: async (client) => {
await client.get('/api/search-index')
.expect(200)
}
}
];

for (let permission of permissions) {
it(`testing permission ${permission}`, async () => {

const relevantCases = cases.filter(c => c.requirePermission === permission);
const forbiddenCases = cases.filter(c => c.requirePermission !== permission);
name: 'search-index',
requirePermission: 'search-index',
action: async client => {
await client.get('/api/search-index').expect(200);
},
},
];

let response = await clients.authoring.post('/api/apps?author.name=test&author.email=test@soluto.com')
.send({ name: 'my-app', permissions: [permission] })
.expect(200);
for (let permission of permissions) {
it(`testing permission ${permission}`, async () => {
const relevantCases = cases.filter(c => c.requirePermission === permission);
const forbiddenCases = cases.filter(c => c.requirePermission !== permission);

let { appId, appSecret } = response.body;
let response = await clients.authoring
.post('/api/apps?author.name=test&author.email=test@soluto.com')
.send({ name: 'my-app', permissions: [permission] })
.expect(200);

let appClient = await clients.authoring.with(client =>
client.set({ "x-client-id": appId, "x-client-secret": appSecret })
.unset("Authorization")
);
let { appId, appSecret } = response.body;

await Promise.all(relevantCases.map(x=> x.action(appClient)))

await Promise.all(forbiddenCases.map(async x=> {
await x.action(appClient).then(()=>true, ex=> {
return expect(ex.message).to.contain("403");
})
}));


}).timeout(6000);
}
let appClient = await clients.authoring.with(client =>
client.set({ 'x-client-id': appId, 'x-client-secret': appSecret }).unset('Authorization'),
);

await Promise.all(relevantCases.map(x => x.action(appClient)));

});
await Promise.all(
forbiddenCases.map(async x => {
await x.action(appClient).then(
() => true,
ex => {
return expect(ex.message).to.contain('403');
},
);
}),
);
}).timeout(6000);
}
});

0 comments on commit e8f8ee9

Please sign in to comment.