Selaa lähdekoodia

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 vuotta sitten
vanhempi
commit
3996c1979d

+ 23 - 5
BulkPrintingAPI/Configuration/DataEncryptionOptions.cs

@@ -12,9 +12,9 @@ namespace BulkPrintingAPI.Configuration
12 12
 
13 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 18
             PasswordNoise = configuration.GetValue("PasswordFuzz", _defaultPasswordNoise);
19 19
         }
20 20
 
@@ -48,11 +48,29 @@ namespace BulkPrintingAPI.Configuration
48 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 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 75
         public string PasswordNoise { get; set; }
58 76
     }

+ 2 - 2
BulkPrintingAPI/Configuration/TokenAuthenticationOptions.cs

@@ -12,8 +12,8 @@ namespace BulkPrintingAPI.Configuration
12 12
             Issuer = configuration.GetValue("Issuer", "bulk");
13 13
             Audience = configuration.GetValue("Audience", "bulk");
14 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 17
             SigningCredentials = new SigningCredentials(Key, SecurityAlgorithms.HmacSha256);
18 18
             EncryptingCredentials = new EncryptingCredentials(Key, "dir",
19 19
                 SecurityAlgorithms.Aes128CbcHmacSha256);

+ 7 - 4
BulkPrintingAPI/Controllers/BatchesController.cs

@@ -1,4 +1,5 @@
1 1
 using BulkPrintingAPI.Configuration;
2
+using BulkPrintingAPI.Pagination;
2 3
 using MAX.Models;
3 4
 using Microsoft.AspNetCore.Mvc;
4 5
 using Microsoft.EntityFrameworkCore;
@@ -13,7 +14,7 @@ using System.Threading.Tasks;
13 14
 namespace BulkPrintingAPI.Controllers
14 15
 {
15 16
     [Produces("application/json")]
16
-    [Route("api/v1/[controller]")]
17
+    [Route("api/[controller]")]
17 18
     public class BatchesController : Controller
18 19
     {
19 20
         public class OrderRequest
@@ -52,11 +53,13 @@ namespace BulkPrintingAPI.Controllers
52 53
         }
53 54
 
54 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 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 65
         [HttpGet("{id}")]

+ 38 - 28
BulkPrintingAPI/Controllers/LoginController.cs

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
3 3
 using Microsoft.AspNetCore.Mvc;
4 4
 using Microsoft.Extensions.Logging;
5 5
 using Newtonsoft.Json;
6
+using Newtonsoft.Json.Serialization;
6 7
 using System;
7 8
 using System.ComponentModel.DataAnnotations;
8 9
 using System.IdentityModel.Tokens.Jwt;
@@ -13,7 +14,7 @@ using System.Threading.Tasks;
13 14
 namespace BulkPrintingAPI.Controllers
14 15
 {
15 16
     [Produces("application/json")]
16
-    [Route("api/v1/[controller]")]
17
+    [Route("api/[controller]")]
17 18
     public class LoginController : Controller
18 19
     {
19 20
         public class LoginRequest
@@ -119,44 +120,53 @@ namespace BulkPrintingAPI.Controllers
119 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 159
                     access_token = encodedJwt,
151 160
                     credentials = new
152 161
                     {
153 162
                         payload = payload,
154
-                        salt = Convert.ToBase64String(derivedUserKey.Salt),
155
-                        iterations = derivedUserKey.IterationCount,
163
+                        salt = Convert.ToBase64String(derivedBytes.Salt),
164
+                        iterations = derivedBytes.IterationCount,
156 165
                         signature = Convert.ToBase64String(signature)
157 166
                     },
158 167
                     expires_in = (int)_tokenAuthenticationOptions.Lifetime.TotalSeconds
159 168
                 });
169
+            }
160 170
         }
161 171
     }
162 172
 }

+ 1 - 1
BulkPrintingAPI/Controllers/ProductsController.cs

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

+ 8 - 5
BulkPrintingAPI/Controllers/VouchersController.cs

@@ -1,4 +1,5 @@
1 1
 using BulkPrintingAPI.Configuration;
2
+using BulkPrintingAPI.Pagination;
2 3
 using MAX.Models;
3 4
 using Microsoft.AspNetCore.Mvc;
4 5
 using Microsoft.EntityFrameworkCore;
@@ -9,7 +10,7 @@ using System.Threading.Tasks;
9 10
 namespace BulkPrintingAPI.Controllers
10 11
 {
11 12
     [Produces("application/json")]
12
-    [Route("api/v1/batches/{batchId}/[controller]")]
13
+    [Route("api/batches/{batchId}/[controller]")]
13 14
     public class VouchersController : Controller
14 15
     {
15 16
         private readonly ILogger _logger;
@@ -25,7 +26,8 @@ namespace BulkPrintingAPI.Controllers
25 26
         }
26 27
 
27 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 32
             if (!ModelState.IsValid)
31 33
             {
@@ -33,8 +35,10 @@ namespace BulkPrintingAPI.Controllers
33 35
             }
34 36
 
35 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 44
         [HttpGet("{sequenceNumber}")]
@@ -57,7 +61,6 @@ namespace BulkPrintingAPI.Controllers
57 61
             return Ok(voucher);
58 62
         }
59 63
 
60
-
61 64
         private IQueryable<Voucher> VouchersForBatchAndVendor(int batchId, int vendorId)
62 65
         {
63 66
             return _context.Vouchers

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

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

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


+ 41 - 0
BulkPrintingAPI/Pagination/Page.cs

@@ -0,0 +1,41 @@
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,7 +25,7 @@ namespace MAX
25 25
         {
26 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,7 +43,7 @@ namespace MAX
43 43
         {
44 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

@@ -0,0 +1,12 @@
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 +6,7 @@
6 6
 
7 7
   <ItemGroup>
8 8
     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
9
+    <PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
9 10
     <PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
10 11
   </ItemGroup>
11 12
 

+ 5 - 0
MAXData/Models/Account.cs

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

+ 4 - 2
MAXData/Models/Voucher.cs

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

+ 2 - 0
MAXData/Models/Warehouse.cs

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