Browse Source

Add paging for batch and voucher lists.
Read encryption keys directly from config rather than deriving them from passwords.
Add date and nonce to login response credentials payload.

Andrew Klopper 8 years ago
parent
commit
3996c1979d

+ 23 - 5
BulkPrintingAPI/Configuration/DataEncryptionOptions.cs

12
 
12
 
13
         public DataEncryptionOptions(IConfiguration configuration)
13
         public DataEncryptionOptions(IConfiguration configuration)
14
         {
14
         {
15
-            DefaultKey = DeriveKey(configuration, "DefaultKey").GetBytes(32);
16
-            DefaultIterations = configuration.GetValue("DefaultIterations", _defaultIterations);
17
-            DefaultSaltLength = configuration.GetValue("DefaultSaltLength", _defaultSaltLength);
15
+            DefaultKey = GetKeyFromConfiguration(configuration, "DefaultKey", 32);
16
+            Iterations = configuration.GetValue("DefaultIterations", _defaultIterations);
17
+            SaltLength = configuration.GetValue("DefaultSaltLength", _defaultSaltLength);
18
             PasswordNoise = configuration.GetValue("PasswordFuzz", _defaultPasswordNoise);
18
             PasswordNoise = configuration.GetValue("PasswordFuzz", _defaultPasswordNoise);
19
         }
19
         }
20
 
20
 
48
             return new Rfc2898DeriveBytes(password, saltSize, iterations);
48
             return new Rfc2898DeriveBytes(password, saltSize, iterations);
49
         }
49
         }
50
 
50
 
51
+        public static byte[] GetKeyFromConfiguration(IConfiguration configuration, string section,
52
+            int keyLength)
53
+        {
54
+            string encodedKey = configuration[section];
55
+            if (string.IsNullOrWhiteSpace(encodedKey))
56
+            {
57
+                throw new ArgumentException(string.Format("Missing '{0}' value", section),
58
+                    nameof(configuration));
59
+            }
60
+            var keyBytes = Convert.FromBase64String(encodedKey);
61
+            if (keyBytes.Length != keyLength)
62
+            {
63
+                throw new ArgumentException(string.Format("Invalid '{0}' length", section),
64
+                    nameof(configuration));
65
+            }
66
+            return keyBytes;
67
+        }
68
+
51
         public byte[] DefaultKey { get; set; }
69
         public byte[] DefaultKey { get; set; }
52
 
70
 
53
-        public int DefaultIterations { get; set; }
71
+        public int Iterations { get; set; }
54
 
72
 
55
-        public int DefaultSaltLength { get; set; }
73
+        public int SaltLength { get; set; }
56
 
74
 
57
         public string PasswordNoise { get; set; }
75
         public string PasswordNoise { get; set; }
58
     }
76
     }

+ 2 - 2
BulkPrintingAPI/Configuration/TokenAuthenticationOptions.cs

12
             Issuer = configuration.GetValue("Issuer", "bulk");
12
             Issuer = configuration.GetValue("Issuer", "bulk");
13
             Audience = configuration.GetValue("Audience", "bulk");
13
             Audience = configuration.GetValue("Audience", "bulk");
14
             Lifetime = TimeSpan.FromSeconds(configuration.GetValue("TokenLifetime", 600));
14
             Lifetime = TimeSpan.FromSeconds(configuration.GetValue("TokenLifetime", 600));
15
-            Key = new SymmetricSecurityKey(DataEncryptionOptions.DeriveKey(configuration, "Key")
16
-                .GetBytes(32));
15
+            Key = new SymmetricSecurityKey(
16
+                DataEncryptionOptions.GetKeyFromConfiguration(configuration, "Key", 32));
17
             SigningCredentials = new SigningCredentials(Key, SecurityAlgorithms.HmacSha256);
17
             SigningCredentials = new SigningCredentials(Key, SecurityAlgorithms.HmacSha256);
18
             EncryptingCredentials = new EncryptingCredentials(Key, "dir",
18
             EncryptingCredentials = new EncryptingCredentials(Key, "dir",
19
                 SecurityAlgorithms.Aes128CbcHmacSha256);
19
                 SecurityAlgorithms.Aes128CbcHmacSha256);

+ 7 - 4
BulkPrintingAPI/Controllers/BatchesController.cs

1
 using BulkPrintingAPI.Configuration;
1
 using BulkPrintingAPI.Configuration;
2
+using BulkPrintingAPI.Pagination;
2
 using MAX.Models;
3
 using MAX.Models;
3
 using Microsoft.AspNetCore.Mvc;
4
 using Microsoft.AspNetCore.Mvc;
4
 using Microsoft.EntityFrameworkCore;
5
 using Microsoft.EntityFrameworkCore;
13
 namespace BulkPrintingAPI.Controllers
14
 namespace BulkPrintingAPI.Controllers
14
 {
15
 {
15
     [Produces("application/json")]
16
     [Produces("application/json")]
16
-    [Route("api/v1/[controller]")]
17
+    [Route("api/[controller]")]
17
     public class BatchesController : Controller
18
     public class BatchesController : Controller
18
     {
19
     {
19
         public class OrderRequest
20
         public class OrderRequest
52
         }
53
         }
53
 
54
 
54
         [HttpGet]
55
         [HttpGet]
55
-        public async Task<IEnumerable<Batch>> GetBatches()
56
+        public async Task<Page<Batch>> GetBatches([FromQuery] int page = 1,
57
+            [FromQuery] int pageSize = 100)
56
         {
58
         {
57
             var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
59
             var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
58
-            return BatchesForVendor(credentials.Vendor.Id)
59
-                .OrderByDescending(b => b.OrderDate);
60
+            return await Page<Batch>.GetPageAsync(
61
+                BatchesForVendor(credentials.Vendor.Id).OrderByDescending(b => b.OrderDate),
62
+                page, pageSize);
60
         }
63
         }
61
 
64
 
62
         [HttpGet("{id}")]
65
         [HttpGet("{id}")]

+ 38 - 28
BulkPrintingAPI/Controllers/LoginController.cs

3
 using Microsoft.AspNetCore.Mvc;
3
 using Microsoft.AspNetCore.Mvc;
4
 using Microsoft.Extensions.Logging;
4
 using Microsoft.Extensions.Logging;
5
 using Newtonsoft.Json;
5
 using Newtonsoft.Json;
6
+using Newtonsoft.Json.Serialization;
6
 using System;
7
 using System;
7
 using System.ComponentModel.DataAnnotations;
8
 using System.ComponentModel.DataAnnotations;
8
 using System.IdentityModel.Tokens.Jwt;
9
 using System.IdentityModel.Tokens.Jwt;
13
 namespace BulkPrintingAPI.Controllers
14
 namespace BulkPrintingAPI.Controllers
14
 {
15
 {
15
     [Produces("application/json")]
16
     [Produces("application/json")]
16
-    [Route("api/v1/[controller]")]
17
+    [Route("api/[controller]")]
17
     public class LoginController : Controller
18
     public class LoginController : Controller
18
     {
19
     {
19
         public class LoginRequest
20
         public class LoginRequest
119
                 encryptingCredentials: _tokenAuthenticationOptions.EncryptingCredentials
120
                 encryptingCredentials: _tokenAuthenticationOptions.EncryptingCredentials
120
             );
121
             );
121
 
122
 
122
-            var derivedUserKey = DataEncryptionOptions.DeriveKey(credentials.Password +
123
-                _dataEncryptionOptions.PasswordNoise, _dataEncryptionOptions.DefaultSaltLength,
124
-                _dataEncryptionOptions.DefaultIterations);
125
-            var userKey = derivedUserKey.GetBytes(32);
126
-
127
-            var payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(
128
-                JsonConvert.SerializeObject(new
129
-                    {
130
-                        user = credentials.User,
131
-                        vendor = credentials.Vendor,
132
-                        encryptedDatabasePassword = Convert.ToBase64String(Utils.AesReEncrypt(
133
-                            credentials.Vendor.EncryptedDatabasePassword,
134
-                            _dataEncryptionOptions.DefaultKey,
135
-                            userKey)),
136
-                        encryptedVoucherKey = Convert.ToBase64String(Utils.AesReEncrypt(
137
-                            credentials.Vendor.EncryptedVoucherKey,
138
-                            _dataEncryptionOptions.DefaultKey,
139
-                            userKey))
140
-                    })));
141
-
142
-            byte[] signature;
143
-            using (var hmac = new System.Security.Cryptography.HMACSHA256() { Key = userKey })
123
+            using (var derivedBytes = DataEncryptionOptions.DeriveKey(credentials.Password +
124
+                _dataEncryptionOptions.PasswordNoise, _dataEncryptionOptions.SaltLength,
125
+                _dataEncryptionOptions.Iterations))
144
             {
126
             {
145
-                signature = hmac.ComputeHash(Encoding.ASCII.GetBytes(payload));
146
-            }
127
+                var userKey = derivedBytes.GetBytes(32);
128
+
129
+                var payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(
130
+                    JsonConvert.SerializeObject(
131
+                        new
132
+                        {
133
+                            date = DateTime.Now,
134
+                            nonce = await _tokenAuthenticationOptions.NonceGenerator(),
135
+                            user = credentials.User,
136
+                            vendor = credentials.Vendor,
137
+                            encryptedDatabasePassword = Convert.ToBase64String(Utils.AesReEncrypt(
138
+                                credentials.Vendor.EncryptedDatabasePassword,
139
+                                _dataEncryptionOptions.DefaultKey,
140
+                                userKey)),
141
+                            encryptedVoucherKey = Convert.ToBase64String(Utils.AesReEncrypt(
142
+                                credentials.Vendor.EncryptedVoucherKey,
143
+                                _dataEncryptionOptions.DefaultKey,
144
+                                userKey))
145
+                        },
146
+                        new JsonSerializerSettings
147
+                        {
148
+                            ContractResolver = new CamelCasePropertyNamesContractResolver()
149
+                        })));
150
+
151
+                byte[] signature;
152
+                using (var hmac = new System.Security.Cryptography.HMACSHA256() { Key = userKey })
153
+                {
154
+                    signature = hmac.ComputeHash(Encoding.ASCII.GetBytes(payload));
155
+                }
147
 
156
 
148
-            return Ok(new
157
+                return Ok(new
149
                 {
158
                 {
150
                     access_token = encodedJwt,
159
                     access_token = encodedJwt,
151
                     credentials = new
160
                     credentials = new
152
                     {
161
                     {
153
                         payload = payload,
162
                         payload = payload,
154
-                        salt = Convert.ToBase64String(derivedUserKey.Salt),
155
-                        iterations = derivedUserKey.IterationCount,
163
+                        salt = Convert.ToBase64String(derivedBytes.Salt),
164
+                        iterations = derivedBytes.IterationCount,
156
                         signature = Convert.ToBase64String(signature)
165
                         signature = Convert.ToBase64String(signature)
157
                     },
166
                     },
158
                     expires_in = (int)_tokenAuthenticationOptions.Lifetime.TotalSeconds
167
                     expires_in = (int)_tokenAuthenticationOptions.Lifetime.TotalSeconds
159
                 });
168
                 });
169
+            }
160
         }
170
         }
161
     }
171
     }
162
 }
172
 }

+ 1 - 1
BulkPrintingAPI/Controllers/ProductsController.cs

8
 namespace BulkPrintingAPI.Controllers
8
 namespace BulkPrintingAPI.Controllers
9
 {
9
 {
10
     [Produces("application/json")]
10
     [Produces("application/json")]
11
-    [Route("api/v1/[controller]")]
11
+    [Route("api/[controller]")]
12
     public class ProductsController : Controller
12
     public class ProductsController : Controller
13
     {
13
     {
14
         private readonly ILogger _logger;
14
         private readonly ILogger _logger;

+ 8 - 5
BulkPrintingAPI/Controllers/VouchersController.cs

1
 using BulkPrintingAPI.Configuration;
1
 using BulkPrintingAPI.Configuration;
2
+using BulkPrintingAPI.Pagination;
2
 using MAX.Models;
3
 using MAX.Models;
3
 using Microsoft.AspNetCore.Mvc;
4
 using Microsoft.AspNetCore.Mvc;
4
 using Microsoft.EntityFrameworkCore;
5
 using Microsoft.EntityFrameworkCore;
9
 namespace BulkPrintingAPI.Controllers
10
 namespace BulkPrintingAPI.Controllers
10
 {
11
 {
11
     [Produces("application/json")]
12
     [Produces("application/json")]
12
-    [Route("api/v1/batches/{batchId}/[controller]")]
13
+    [Route("api/batches/{batchId}/[controller]")]
13
     public class VouchersController : Controller
14
     public class VouchersController : Controller
14
     {
15
     {
15
         private readonly ILogger _logger;
16
         private readonly ILogger _logger;
25
         }
26
         }
26
 
27
 
27
         [HttpGet]
28
         [HttpGet]
28
-        public async Task<IActionResult> GetVouchers([FromRoute] int batchId)
29
+        public async Task<IActionResult> GetVouchers([FromRoute] int batchId,
30
+            [FromQuery] int page = 1, [FromQuery] int pageSize = 100)
29
         {
31
         {
30
             if (!ModelState.IsValid)
32
             if (!ModelState.IsValid)
31
             {
33
             {
33
             }
35
             }
34
 
36
 
35
             var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
37
             var credentials = await Utils.GetLoginCredentialsFromRequest(HttpContext, _context);
36
-            return Ok(VouchersForBatchAndVendor(batchId, credentials.Vendor.Id)
37
-                .OrderBy(v => new { v.BatchId, v.SequenceNumber }));
38
+            return Ok(await Page<Voucher>.GetPageAsync(
39
+                VouchersForBatchAndVendor(batchId, credentials.Vendor.Id)
40
+                    .OrderBy(v => new { v.BatchId, v.SequenceNumber }),
41
+                page, pageSize));
38
         }
42
         }
39
 
43
 
40
         [HttpGet("{sequenceNumber}")]
44
         [HttpGet("{sequenceNumber}")]
57
             return Ok(voucher);
61
             return Ok(voucher);
58
         }
62
         }
59
 
63
 
60
-
61
         private IQueryable<Voucher> VouchersForBatchAndVendor(int batchId, int vendorId)
64
         private IQueryable<Voucher> VouchersForBatchAndVendor(int batchId, int vendorId)
62
         {
65
         {
63
             return _context.Vouchers
66
             return _context.Vouchers

+ 1 - 1
BulkPrintingAPI/Migrations/20170705113945_InitialCreate.Designer.cs

8
 namespace BulkPrintingAPI.Migrations
8
 namespace BulkPrintingAPI.Migrations
9
 {
9
 {
10
     [DbContext(typeof(MAXContext))]
10
     [DbContext(typeof(MAXContext))]
11
-    [Migration("20170705113945_InitialCreate")]
11
+    [Migration("20170705205151_InitialCreate")]
12
     partial class InitialCreate
12
     partial class InitialCreate
13
     {
13
     {
14
         protected override void BuildTargetModel(ModelBuilder modelBuilder)
14
         protected override void BuildTargetModel(ModelBuilder modelBuilder)

BulkPrintingAPI/Migrations/20170705113945_InitialCreate.cs → BulkPrintingAPI/Migrations/20170705205151_InitialCreate.cs


+ 41 - 0
BulkPrintingAPI/Pagination/Page.cs

1
+using Microsoft.EntityFrameworkCore;
2
+using System.Collections.Generic;
3
+using System.Linq;
4
+using System.Threading.Tasks;
5
+
6
+namespace BulkPrintingAPI.Pagination
7
+{
8
+    public class Page<T>
9
+    {
10
+        public Page(List<T> items, int pageNumber, int pageSize, int totalItems)
11
+        {
12
+            Items = items;
13
+            PageNumber = pageNumber;
14
+            PageSize = pageSize;
15
+            TotalItems = totalItems;
16
+            NumPages = totalItems / pageSize + (totalItems % pageSize > 0 ? 1 : 0);
17
+        }
18
+
19
+        public List<T> Items { get; private set; }
20
+
21
+        public int TotalItems { get; private set; }
22
+
23
+        public int PageNumber { get; private set; }
24
+
25
+        public int PageSize { get; private set; }
26
+
27
+        public int NumPages { get; private set; }
28
+
29
+        public static async Task<Page<T>> GetPageAsync(IQueryable<T> query,
30
+            int pageNumber, int pageSize)
31
+        {
32
+            int count = await query.CountAsync().ConfigureAwait(false);
33
+            var items = await query
34
+                .Skip((pageNumber - 1) * pageSize)
35
+                .Take(pageSize)
36
+                .ToListAsync()
37
+                .ConfigureAwait(false);
38
+            return new Page<T>(items, pageNumber, pageSize, count);
39
+        }
40
+    }
41
+}

+ 2 - 2
MAXClient/Utils.cs

25
         {
25
         {
26
             using (var decryptor = des.CreateDecryptor(des.Key, des.IV))
26
             using (var decryptor = des.CreateDecryptor(des.Key, des.IV))
27
             {
27
             {
28
-                return Encoding.UTF8.GetString(Utils.Transform(decryptor, Convert.FromBase64String(cipherText)));
28
+                return Encoding.UTF8.GetString(Transform(decryptor, Convert.FromBase64String(cipherText)));
29
             }
29
             }
30
         }
30
         }
31
 
31
 
43
         {
43
         {
44
             using (var encryptor = des.CreateEncryptor(des.Key, des.IV))
44
             using (var encryptor = des.CreateEncryptor(des.Key, des.IV))
45
             {
45
             {
46
-                return Convert.ToBase64String(Utils.Transform(encryptor, Encoding.UTF8.GetBytes(plainText)));
46
+                return Convert.ToBase64String(Transform(encryptor, Encoding.UTF8.GetBytes(plainText)));
47
             }
47
             }
48
         }
48
         }
49
 
49
 

+ 12 - 0
MAXData/Json/DateFormatConverter.cs

1
+using Newtonsoft.Json.Converters;
2
+
3
+namespace MAX.Json
4
+{
5
+    public class DateFormatConverter : IsoDateTimeConverter
6
+    {
7
+        public DateFormatConverter(string format)
8
+        {
9
+            DateTimeFormat = format;
10
+        }
11
+    }
12
+}

+ 1 - 0
MAXData/MAXModels.csproj

6
 
6
 
7
   <ItemGroup>
7
   <ItemGroup>
8
     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
8
     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
9
+    <PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
9
     <PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
10
     <PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
10
   </ItemGroup>
11
   </ItemGroup>
11
 
12
 

+ 5 - 0
MAXData/Models/Account.cs

1
 using System.Collections.Generic;
1
 using System.Collections.Generic;
2
 using System.ComponentModel.DataAnnotations;
2
 using System.ComponentModel.DataAnnotations;
3
 using System.ComponentModel.DataAnnotations.Schema;
3
 using System.ComponentModel.DataAnnotations.Schema;
4
+using System.Runtime.Serialization;
4
 
5
 
5
 namespace MAX.Models
6
 namespace MAX.Models
6
 {
7
 {
25
         [Required, MaxLength(50)]
26
         [Required, MaxLength(50)]
26
         public string Reference { get; set; }
27
         public string Reference { get; set; }
27
 
28
 
29
+        [IgnoreDataMember]
28
         public int WarehouseId { get; set; }
30
         public int WarehouseId { get; set; }
31
+
29
         public Warehouse Warehouse { get; set; }
32
         public Warehouse Warehouse { get; set; }
30
 
33
 
34
+        [IgnoreDataMember]
31
         public ICollection<User> Users { get; set; }
35
         public ICollection<User> Users { get; set; }
32
 
36
 
37
+        [IgnoreDataMember]
33
         public ICollection<Vendor> Vendors { get; set; }
38
         public ICollection<Vendor> Vendors { get; set; }
34
 
39
 
35
         public decimal Balance { get; set; }
40
         public decimal Balance { get; set; }

+ 4 - 2
MAXData/Models/Voucher.cs

1
-using System;
1
+using MAX.Json;
2
+using Newtonsoft.Json;
3
+using System;
2
 using System.ComponentModel.DataAnnotations;
4
 using System.ComponentModel.DataAnnotations;
3
 using System.ComponentModel.DataAnnotations.Schema;
5
 using System.ComponentModel.DataAnnotations.Schema;
4
 using System.Runtime.Serialization;
6
 using System.Runtime.Serialization;
18
 
20
 
19
         public int SequenceNumber { get; set; }
21
         public int SequenceNumber { get; set; }
20
 
22
 
21
-        [Column(TypeName = "Date")]
23
+        [Column(TypeName = "Date"), JsonConverter(typeof(DateFormatConverter), "yyyy-MM-dd")]
22
         public DateTime ExpiryDate { get; set; }
24
         public DateTime ExpiryDate { get; set; }
23
         
25
         
24
         [Required, MaxLength(50)]
26
         [Required, MaxLength(50)]

+ 2 - 0
MAXData/Models/Warehouse.cs

1
 using System.Collections.Generic;
1
 using System.Collections.Generic;
2
 using System.ComponentModel.DataAnnotations;
2
 using System.ComponentModel.DataAnnotations;
3
 using System.ComponentModel.DataAnnotations.Schema;
3
 using System.ComponentModel.DataAnnotations.Schema;
4
+using System.Runtime.Serialization;
4
 
5
 
5
 namespace MAX.Models
6
 namespace MAX.Models
6
 {
7
 {
12
         [Required, MaxLength(50)]
13
         [Required, MaxLength(50)]
13
         public string Name { get; set; }
14
         public string Name { get; set; }
14
 
15
 
16
+        [IgnoreDataMember]
15
         public ICollection<Account> Accounts { get; set; }
17
         public ICollection<Account> Accounts { get; set; }
16
     }
18
     }
17
 }
19
 }