From 62503068c6a4f24713edf21b400681d0fa3f5600 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 7 Mar 2019 09:13:39 -0500 Subject: [PATCH] events apis --- .../Public/Controllers/EventsController.cs | 73 +++++++++++++++++ .../Public/Request/EventFilterRequestModel.cs | 51 ++++++++++++ .../Api/Public/Response/EventResponseModel.cs | 82 +++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 src/Api/Public/Controllers/EventsController.cs create mode 100644 src/Core/Models/Api/Public/Request/EventFilterRequestModel.cs create mode 100644 src/Core/Models/Api/Public/Response/EventResponseModel.cs diff --git a/src/Api/Public/Controllers/EventsController.cs b/src/Api/Public/Controllers/EventsController.cs new file mode 100644 index 0000000000..9e3b7727e1 --- /dev/null +++ b/src/Api/Public/Controllers/EventsController.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Bit.Core; +using Bit.Core.Models.Api.Public; +using Bit.Core.Models.Data; +using Bit.Core.Repositories; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.Public.Controllers +{ + [Route("public/events")] + [Authorize("Organization")] + public class EventsController : Controller + { + private readonly IEventRepository _eventRepository; + private readonly ICipherRepository _cipherRepository; + private readonly CurrentContext _currentContext; + + public EventsController( + IEventRepository eventRepository, + ICipherRepository cipherRepository, + CurrentContext currentContext) + { + _eventRepository = eventRepository; + _cipherRepository = cipherRepository; + _currentContext = currentContext; + } + + /// + /// List all events. + /// + /// + /// Returns a filtered list of your organization's event logs, paged by a continuation token. + /// If no filters are provided, it will return the last 30 days of event for the organization. + /// + [HttpGet] + [ProducesResponseType(typeof(ListResponseModel), (int)HttpStatusCode.OK)] + public async Task List([FromQuery]EventFilterRequestModel request) + { + var dateRange = request.ToDateRange(); + var result = new PagedResult(); + if(request.ActingUserId.HasValue) + { + result = await _eventRepository.GetManyByOrganizationActingUserAsync( + _currentContext.OrganizationId.Value, request.ActingUserId.Value, dateRange.Item1, dateRange.Item2, + new PageOptions { ContinuationToken = request.ContinuationToken }); + } + else if(request.ItemId.HasValue) + { + var cipher = await _cipherRepository.GetByIdAsync(request.ItemId.Value); + if(cipher != null && cipher.OrganizationId == _currentContext.OrganizationId.Value) + { + result = await _eventRepository.GetManyByCipherAsync( + cipher, dateRange.Item1, dateRange.Item2, + new PageOptions { ContinuationToken = request.ContinuationToken }); + } + } + else + { + result = await _eventRepository.GetManyByOrganizationAsync( + _currentContext.OrganizationId.Value, dateRange.Item1, dateRange.Item2, + new PageOptions { ContinuationToken = request.ContinuationToken }); + } + + var eventResponses = result.Data.Select(e => new EventResponseModel(e)); + var response = new ListResponseModel(eventResponses); + return new JsonResult(response); + } + } +} diff --git a/src/Core/Models/Api/Public/Request/EventFilterRequestModel.cs b/src/Core/Models/Api/Public/Request/EventFilterRequestModel.cs new file mode 100644 index 0000000000..5d1f724568 --- /dev/null +++ b/src/Core/Models/Api/Public/Request/EventFilterRequestModel.cs @@ -0,0 +1,51 @@ +using System; +using Bit.Core.Exceptions; + +namespace Bit.Core.Models.Api.Public +{ + public class EventFilterRequestModel + { + /// + /// The start date. Must be less than the end date. + /// + public DateTime? Start { get; set; } + /// + /// The end date. Must be greater than the start date. + /// + public DateTime? End { get; set; } + /// + /// The unique identifier of the user that performed the event. + /// + public Guid? ActingUserId { get; set; } + /// + /// The unique identifier of the related item that the event describes. + /// + public Guid? ItemId { get; set; } + /// + /// A cursor for use in pagination. + /// + public string ContinuationToken { get; set; } + + public Tuple ToDateRange() + { + if(!End.HasValue || !Start.HasValue) + { + End = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1); + Start = DateTime.UtcNow.Date.AddDays(-30); + } + else if(Start.Value > End.Value) + { + var newEnd = Start; + Start = End; + End = newEnd; + } + + if((End.Value - Start.Value) > TimeSpan.FromDays(367)) + { + throw new BadRequestException("Date range must be < 367 days."); + } + + return new Tuple(Start.Value, End.Value); + } + } +} diff --git a/src/Core/Models/Api/Public/Response/EventResponseModel.cs b/src/Core/Models/Api/Public/Response/EventResponseModel.cs new file mode 100644 index 0000000000..c9575151e1 --- /dev/null +++ b/src/Core/Models/Api/Public/Response/EventResponseModel.cs @@ -0,0 +1,82 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Bit.Core.Enums; +using Bit.Core.Models.Data; + +namespace Bit.Core.Models.Api.Public +{ + /// + /// An event log. + /// + public class EventResponseModel : IResponseModel + { + public EventResponseModel(IEvent ev) + { + if(ev == null) + { + throw new ArgumentNullException(nameof(ev)); + } + + Type = ev.Type; + ItemId = ev.CipherId; + CollectionId = ev.CollectionId; + GroupId = ev.GroupId; + MemberId = ev.OrganizationUserId; + ActingUserId = ev.ActingUserId; + Date = ev.Date; + Device = ev.DeviceType; + IpAddress = ev.IpAddress; + } + + /// + /// String representing the object's type. Objects of the same type share the same properties. + /// + /// event + [Required] + public string Object => "event"; + /// + /// The type of event. + /// + [Required] + public EventType Type { get; set; } + /// + /// The unique identifier of the related item that the event describes. + /// + /// 3767a302-8208-4dc6-b842-030428a1cfad + public Guid? ItemId { get; set; } + /// + /// The unique identifier of the related collection that the event describes. + /// + /// bce212a4-25f3-4888-8a0a-4c5736d851e0 + public Guid? CollectionId { get; set; } + /// + /// The unique identifier of the related group that the event describes. + /// + /// f29a2515-91d2-4452-b49b-5e8040e6b0f4 + public Guid? GroupId { get; set; } + /// + /// The unique identifier of the related member that the event describes. + /// + /// e68b8629-85eb-4929-92c0-b84464976ba4 + public Guid? MemberId { get; set; } + /// + /// The unique identifier of the user that performed the event. + /// + /// a2549f79-a71f-4eb9-9234-eb7247333f94 + public Guid? ActingUserId { get; set; } + /// + /// The date/timestamp when the event occurred. + /// + [Required] + public DateTime Date { get; set; } + /// + /// The type of device used by the acting user when the event occurred. + /// + public DeviceType? Device { get; set; } + /// + /// The IP address of the acting user. + /// + /// 172.16.254.1 + public string IpAddress { get; set; } + } +}