Skip to content

Commit

Permalink
feat: add security
Browse files Browse the repository at this point in the history
- add custom JWT authorization
- map the Neo4J query result back to a poco collection
  • Loading branch information
psyphore committed Sep 20, 2019
1 parent d9e443b commit fe52866
Show file tree
Hide file tree
Showing 18 changed files with 411 additions and 57 deletions.
8 changes: 2 additions & 6 deletions BusinessServices/Examples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,11 @@ public static List<int> OddNumbers(int l, int r)
var result = new List<int>();
if (l >= 1 && r <= Math.Abs(Math.Pow(10, 5)))
{
for (var index = l; index < r+1; index += 1)
{
for (var index = l; index < r + 1; index += 1)
if (Math.Abs(index % 2) != 0)
{
result.Add(index);
}
}
}
return result;
}
}
}
}
123 changes: 118 additions & 5 deletions DataAccess/Person/PersonQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,43 @@ public IDictionary<string, string> Mutations
{
return new Dictionary<string, string>
{
{ "MERGE_PERSON", @"CREATE (p:Person) SET p = $p RETURN p"},
{ "DELETE_PERSON", @""},
{ "DEACTIVATE_PERSON", @""}
{ "UPDATE_PERSON", @"
MERGE (p:Person{id: $id})
SET p += {
title: $title
, firstname: $firstname
, lastname: $lastname
, mobile: $mobile
, email: $email
, avatar: $avatar
, bio: $bio
, subscriptions: $subscriptions
}
RETURN p AS person;
"},
{ "UPDATE_PERSON_2", @"
MERGE (p:Person{id: $id})
SET p += {
mobile: $mobile
, email: $email
, bio: $bio
, knownAs: $knownAs
}
RETURN p AS person;
"},
{ "UPDATE_PERSON_REPORTING", @"
MATCH (m:Person{id:$man}-[r:MANAGES]->(s:Person{id:$sub}))
MERGE (m)-[r2:MANAGES]->(s)
WITH r, s, m, r2
DELETE r
RETURN s
"},
{ "DEACTIVATE_PERSON", @"
MATCH (p:Person)
WHERE p.id = $id
SET p.deactivated = timestamp()
RETURN p
" }
};
}
}
Expand All @@ -23,8 +57,87 @@ public IDictionary<string, string> Queries
{
return new Dictionary<string, string>
{
{ "GET_PERSON", @"MATCH (p:Person {id = $id}) RETURN p"},
{ "GET_PEOPLE", @"MATCH (p:Person) RETURN p" }
{ "GET_PERSON", @"
OPTIONAL MATCH (p:Person{id: $id})
WITH apoc.date.format(p.deactivated, 'yyyyMMdd HH:mm:ss.ms') AS deactivated, p
RETURN p {
.firstname,
.mobile,
.bio,
.id,
.title,
.email,
.lastname,
.avatar,
.knownAs,
manager: apoc.cypher.runFirstColumn(""MATCH (m)-[:MANAGES]->(this) RETURN m LIMIT 1"", {this: p}, false),
team: [(p)<-[:MANAGES]-()-[:MANAGES]->(t) | t],
line: [(s) < -[:MANAGES] - (p) | s],
products: [(p) -[:KNOWS]->(pr) | pr],
building: [(p) -[:BASED_IN]->(b) | b],
deactivated: deactivated
} AS person
"},
{ "GET_PEOPLE", @"
MATCH (p:Person)
WITH apoc.date.format(p.deactivated, 'yyyyMMdd HH:mm:ss.ms') AS deactivated, p
RETURN p {
.firstname,
.mobile,
.bio,
.id,
.title,
.email,
.lastname,
.avatar,
.knownAs,
manager: apoc.cypher.runFirstColumn(""MATCH (m)-[:MANAGES]->(this) RETURN m LIMIT 1"", {this: p}, false),
team: [(p)<-[:MANAGES]-()-[:MANAGES]->(t) | t],
line: [(s) < -[:MANAGES] - (p) | s],
products: [(p) -[:KNOWS]->(pr) | pr],
building: [(p) -[:BASED_IN]->(b) | b],
deactivated: deactivated
} AS person
ORDER BY person.lastname ASC, person.firstname ASC
SKIP {offset}
LIMIT {first}
" },
{ "GET_ME", @"
MATCH (p:Person)
WHERE LOWER($firstname) CONTAINS LOWER(p.firstname) AND
LOWER($lastname) CONTAINS LOWER(p.lastname) AND
LOWER($email) CONTAINS LOWER(p.email)
WITH apoc.date.format(p.deactivated, 'yyyyMMdd HH:mm:ss.ms') AS deactivated, p
RETURN p {
.firstname,
.mobile,
.bio,
.id,
.title,
.email,
.lastname,
.avatar,
.knownAs,
manager: apoc.cypher.runFirstColumn(""MATCH (m)-[:MANAGES]->(this) RETURN m LIMIT 1"", {this: p}, false),
team: [(p)<-[:MANAGES]-()-[:MANAGES]->(t) | t],
line: [(s) < -[:MANAGES] - (p) | s],
products: [(p) -[:KNOWS]->(pr) | pr],
building: [(p) -[:BASED_IN]->(b) | b],
subscriptions: ['meals', 'support', 'leave', 'general'],
deactivated: deactivated
} AS person
" },
{ "GET_FUZZY_PERSON", @"
OPTIONAL MATCH (p:Person)
WHERE LOWER($firstname) CONTAINS LOWER(p.firstname) AND
LOWER($lastname) CONTAINS LOWER(p.lastname)
RETURN p AS person
" },
{ "PERSONAL_NOTES", @"
WITH apoc.create.uuid() AS uuid
WITH {id: uuid, subject: 'Notification', body: 'test note'} AS note
RETURN [note] AS notes
" }
};
}
}
Expand Down
18 changes: 12 additions & 6 deletions DataAccess/Person/PersonRepository.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using DataAccess.Interfaces;
using System.Collections.Generic;
using System.Threading.Tasks;
using Neo4j.Driver.V1;
using Newtonsoft.Json;

namespace DataAccess.Person
{
Expand All @@ -19,31 +21,35 @@ public PersonRepository(IRepository repository)

public async Task<Person> Add(Person person)
{
var entity = await _repository.Write<Person>(_mutations["MERGE_PERSON"], person);
var entity = await _repository.Write<Person>(_mutations["UPDATE_PERSON"].Trim(), person);
return entity;
}

public async Task<IEnumerable<Person>> All()
{
var entity = await _repository.Read<IEnumerable<Person>>(_queries["GET_PEOPLE"], null);
return entity;
var x = new List<Person>();
var entity = await _repository.Read<object>(_queries["GET_PEOPLE"].Trim(), new { first = 9999, offset = 0 });
if (entity != null)
x = JsonConvert.DeserializeObject<List<Person>>(JsonConvert.SerializeObject(entity));

return x;
}

public async Task<string> Delete(string id)
{
var entity = await _repository.Write<Person>(_mutations["DELETE_PERSON"], id);
var entity = await _repository.Write<Person>(_mutations["DEACTIVATE_PERSON"].Trim(), new { id });
return entity.Id;
}

public async Task<Person> Get(string id)
{
var entity = await _repository.Read<Person>(_queries["GET_PERSON"], id);
var entity = await _repository.Read<Person>(_queries["GET_PERSON"].Trim(), new { id });
return entity;
}

public async Task<Person> Update(Person person)
{
var entity = await _repository.Write<Person>(_mutations["MERGE_PERSON"], person);
var entity = await _repository.Write<Person>(_mutations["UPDATE_PERSON_2"].Trim(), person);
return entity;
}
}
Expand Down
15 changes: 8 additions & 7 deletions DataAccess/Repository.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using DataAccess.Interfaces;
using Models.DTOs.Configuration;
using Neo4j.Driver.V1;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace DataAccess
Expand All @@ -14,14 +15,14 @@ public Repository(Connection connection)
_driver = GraphDatabase.Driver(connection.BoltURL, AuthTokens.Basic(connection.Username, connection.Password));
}

public ISession GetSession(AccessMode mode)
public void Dispose()
{
return _driver.Session(mode);
_driver?.Dispose();
}

public void Dispose()
public ISession GetSession(AccessMode mode)
{
_driver?.Dispose();
return _driver.Session(mode);
}

public async Task<T> Read<T>(string query, object parameters)
Expand All @@ -30,8 +31,8 @@ public async Task<T> Read<T>(string query, object parameters)
{
var trx = await session.ReadTransactionAsync(async tx =>
{
var response = await tx.RunAsync(query, parameters);
return response.SingleAsync().As<T>();
var response = await tx.RunAsync(query,parameters);
return response.ToListAsync().As<T>();
});
return trx;
}
Expand All @@ -44,7 +45,7 @@ public async Task<T> Write<T>(string query, object parameters)
var trx = await session.WriteTransactionAsync(async tx =>
{
var response = await tx.RunAsync(query, parameters);
return response.SingleAsync().As<T>();
return response.ToListAsync().As<T>();
});
return trx;
}
Expand Down
1 change: 1 addition & 0 deletions GraphQLCore/GraphQLCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="9.0.0" />
<PackageReference Include="GraphQL" Version="2.4.0" />
<PackageReference Include="GraphQL.Authorization" Version="2.1.29" />
<PackageReference Include="GraphQL.Common" Version="1.0.3" />
</ItemGroup>

Expand Down
4 changes: 4 additions & 0 deletions IoC/IoC.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<ItemGroup>
<PackageReference Include="AutoMapper" Version="9.0.0" />
<PackageReference Include="GraphQL.Authorization" Version="2.1.29" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
<!--<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />-->
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
Expand All @@ -26,6 +27,9 @@
</ItemGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Authorization">
<HintPath>C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.authorization\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Authorization.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.Http.Abstractions">
<HintPath>C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.abstractions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Abstractions.dll</HintPath>
</Reference>
Expand Down
51 changes: 46 additions & 5 deletions IoC/RegisterGraphQLHandlers.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
using GraphQL;
using GraphQL.Http;
using GraphQL.Types;
using GraphQL.Validation;
using GraphQLCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Models.GraphQLTypes.Person;
using Models.Types;
using System.Threading.Tasks;

namespace IoC
{
internal class RegisterGraphQLHandlers
internal static class RegisterGraphQLHandlers
{
public static void RegisterTypes(IServiceCollection services)
public static void ConfigureGraphQLServices(this IServiceCollection services)
{
services.AddSingleton<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
Expand All @@ -20,13 +23,51 @@ public static void RegisterTypes(IServiceCollection services)
services.AddSingleton<PersonQuery>();
services.AddSingleton<PersonMutation>();
services.AddSingleton<PersonType>();
services.AddSingleton<PersonInputType>();

// GraphQL Schema
services.AddSingleton<ISchema, MainSchema>();

//var sp = services.BuildServiceProvider();
//using (var mainSchema = new MainSchema(new FuncDependencyResolver(type => sp.GetService(type))))
// services.AddSingleton<ISchema>(mainSchema);
services.AddGraphQLAuth();
}

public static void UseGraphQLAuth(this IApplicationBuilder app)
{
var settings = new GraphQLSettings
{
Path = "/graphql",
BuildUserContext = ctx =>
{
var userContext = new GraphQLUserContext
{
User = ctx.User
};

return Task.FromResult(userContext);
},
EnableMetrics = true
};

var rules = app.ApplicationServices.GetServices<IValidationRule>();
settings.ValidationRules.AddRange(rules);

app.UseMiddleware<GraphQLMiddleware>(settings);
}

public static void AddGraphQLAuth(this IServiceCollection services)
{
//services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//services.TryAddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>();
//services.AddTransient<IValidationRule, AuthorizationValidationRule>();

//services.TryAddSingleton(s =>
//{
// var authSettings = new AuthorizationSettings();

// authSettings.AddPolicy("AdminPolicy", _ => _.RequireClaim("role", "Admin"));

// return authSettings;
//});
}
}
}
Loading

0 comments on commit fe52866

Please sign in to comment.