소스 검색

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 년 전
부모
커밋
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
 }