diff --git a/src/Yavsc.Blogs/Controllers/BlogAclApiController.cs b/src/Yavsc.Blogs/Controllers/BlogAclApiController.cs
index 3fbd89cf..aa81f9d5 100644
--- a/src/Yavsc.Blogs/Controllers/BlogAclApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/BlogAclApiController.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@@ -18,11 +19,19 @@ namespace Yavsc.Blogs.Controllers
_context = context;
}
- // GET: api/BlogAclApi
+ ///
+ /// Returns the ACL entries for the caller's own blog posts.
+ /// Blog posts (and therefore their ACLs) are private to their
+ /// author — the API never exposes another user's ACL.
+ ///
+ // GET: api/blogacl
[HttpGet]
public IEnumerable GetBlogACL()
{
- return _context.CircleAuthorizationToBlogPost;
+ var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ return _context.CircleAuthorizationToBlogPost
+ .Include(a => a.Allowed)
+ .Where(a => a.Allowed.OwnerId == uid);
}
// GET: api/BlogAclApi/5
diff --git a/src/Yavsc.Blogs/Controllers/CircleApiController.cs b/src/Yavsc.Blogs/Controllers/CircleApiController.cs
index b5434f83..368b488a 100644
--- a/src/Yavsc.Blogs/Controllers/CircleApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/CircleApiController.cs
@@ -1,3 +1,5 @@
+using System.Linq;
+using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Yavsc.Models;
@@ -17,14 +19,22 @@ namespace Yavsc.Blogs.Controllers
_context = context;
}
- // GET: api/CircleApi
+ ///
+ /// Returns the caller's own circles. Circles are personal —
+ /// the API never exposes another user's circles, even by id.
+ ///
+ // GET: api/circle
[HttpGet]
public IEnumerable GetCircle()
{
- return _context.Circle;
+ var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ return _context.Circle.Where(c => c.OwnerId == uid);
}
- // GET: api/CircleApi/5
+ ///
+ /// Returns a single circle only when it belongs to the caller.
+ ///
+ // GET: api/circle/5
[HttpGet("{id}", Name = "GetCircle")]
public async Task GetCircle([FromRoute] long id)
{
@@ -33,7 +43,9 @@ namespace Yavsc.Blogs.Controllers
return BadRequest(ModelState);
}
- Circle circle = await _context.Circle.SingleAsync(m => m.Id == id);
+ var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ Circle circle = await _context.Circle.SingleOrDefaultAsync(
+ m => m.Id == id && m.OwnerId == uid);
if (circle == null)
{
@@ -43,7 +55,12 @@ namespace Yavsc.Blogs.Controllers
return Ok(circle);
}
- // PUT: api/CircleApi/5
+ ///
+ /// Replaces a circle. The caller must own it; the server
+ /// reasserts ownership regardless of any OwnerId the client
+ /// tries to put in the body.
+ ///
+ // PUT: api/circle/5
[HttpPut("{id}")]
public async Task PutCircle([FromRoute] long id, [FromBody] Circle circle)
{
@@ -57,6 +74,16 @@ namespace Yavsc.Blogs.Controllers
return BadRequest();
}
+ var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ var existing = await _context.Circle.SingleOrDefaultAsync(
+ c => c.Id == id && c.OwnerId == uid);
+ if (existing is null)
+ {
+ return new ChallengeResult();
+ }
+
+ // Force OwnerId to the caller; the body value is ignored.
+ circle.OwnerId = uid;
_context.Entry(circle).State = EntityState.Modified;
try
@@ -78,7 +105,11 @@ namespace Yavsc.Blogs.Controllers
return new StatusCodeResult(StatusCodes.Status204NoContent);
}
- // POST: api/CircleApi
+ ///
+ /// Creates a circle owned by the caller. The server overwrites
+ /// any OwnerId the client sends in the body.
+ ///
+ // POST: api/circle
[HttpPost]
public async Task PostCircle([FromBody] Circle circle)
{
@@ -87,6 +118,9 @@ namespace Yavsc.Blogs.Controllers
return BadRequest(ModelState);
}
+ var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ circle.OwnerId = uid;
+
_context.Circle.Add(circle);
try
{
@@ -107,7 +141,13 @@ namespace Yavsc.Blogs.Controllers
return CreatedAtRoute("GetCircle", new { id = circle.Id }, circle);
}
- // DELETE: api/CircleApi/5
+ ///
+ /// Deletes a circle only if the caller owns it. Returns 404
+ /// (not 403) when the circle does not exist or is not owned
+ /// by the caller, to avoid leaking the existence of someone
+ /// else's circle.
+ ///
+ // DELETE: api/circle/5
[HttpDelete("{id}")]
public async Task DeleteCircle([FromRoute] long id)
{
@@ -116,7 +156,9 @@ namespace Yavsc.Blogs.Controllers
return BadRequest(ModelState);
}
- Circle circle = await _context.Circle.SingleAsync(m => m.Id == id);
+ var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ Circle circle = await _context.Circle.SingleOrDefaultAsync(
+ m => m.Id == id && m.OwnerId == uid);
if (circle == null)
{
return NotFound();