Пример #1
0
        public override async Task <bool> Handle()
        {
            if (EntityData.IsActivity(MainObject.Data))
            {
                return(true);
            }
            var data = MainObject.Data;

            var createActivity = new ASObject();

            createActivity.Type.Add("https://www.w3.org/ns/activitystreams#Create");
            createActivity["to"].AddRange(data["to"]);
            createActivity["bto"].AddRange(data["bto"]);
            createActivity["cc"].AddRange(data["cc"]);
            createActivity["bcc"].AddRange(data["bcc"]);
            createActivity["audience"].AddRange(data["audience"]);
            createActivity["actor"].Add(ASTerm.MakeId(Actor.Id));
            createActivity["object"].Add(ASTerm.MakeId(MainObject.Id));
            createActivity.Id = await _urlService.FindUnusedID(EntityStore, createActivity);

            var activityEntity = await EntityStore.StoreEntity(APEntity.From(createActivity, true));

            MainObject = activityEntity;

            return(true);
        }
Пример #2
0
        public override async Task <bool> Handle()
        {
            if (MainObject.Type != "Delete")
            {
                return(true);
            }

            var oldObject = await EntityStore.GetEntity((string)MainObject.Data["object"].Single().Primitive, true);

            var newData = oldObject.Data;

            foreach (var kv in newData)
            {
                if (!DeleteWhitelist.Contains(kv.Key))
                {
                    kv.Value.Clear();
                }
            }

            newData.Replace("type", new ASTerm("Tombstone"));
            newData.Replace("deleted", new ASTerm(DateTime.Now.ToString("o")));

            var newObject = APEntity.From(newData);
            await EntityStore.StoreEntity(newObject);

            return(true);
        }
Пример #3
0
        public async Task <APEntity> GetEntity(string id, bool doRemote)
        {
            string query    = null;
            string parsedId = null;

            if (id.Contains("?"))
            {
                var split = id.Split(new char[] { '?' }, 2);
                query    = split[1];
                parsedId = split[0];
            }

            APEntity original;
            var      entity = original = await Bypass.GetEntity(id, false);

            if (entity == null && parsedId != null)
            {
                entity = await Bypass.GetEntity(parsedId, false);
            }
            if (entity == null || !entity.IsOwner)
            {
                return(doRemote ? await Bypass.GetEntity(id, true) : original);
            }
            if (!entity.Type.StartsWith("_") && entity.Type != "OrderedCollection")
            {
                return(doRemote ? await Bypass.GetEntity(id, true) : original);
            }

            if (query == null)
            {
                return(APEntity.From(await _buildCollection(entity), true));
            }
            else
            {
                int from_id = int.MaxValue;
                int to_id   = int.MinValue;
                foreach (var item in query.Split('&'))
                {
                    var kv = item.Split('=');
                    if (kv[0] == "from_id" && kv.Length > 1)
                    {
                        from_id = int.Parse(kv[1]);
                    }
                    if (kv[0] == "to_id" && kv.Length > 1)
                    {
                        to_id = int.Parse(kv[1]);
                    }
                }

                return(APEntity.From(await _buildPage(entity, from_id, to_id)));
            }
        }
Пример #4
0
        private async Task <APEntity> _newCollection(string type, string attributedTo)
        {
            var obj = new ASObject();

            obj["type"].Add(new ASTerm("OrderedCollection"));
            obj["attributedTo"].Add(new ASTerm(attributedTo));
            obj.Replace("id", new ASTerm(await _entityData.FindUnusedID(_entityStore, obj, type, attributedTo)));
            var entity = APEntity.From(obj, true);

            entity.Type = "_" + type;
            entity      = await _entityStore.StoreEntity(entity);

            await _entityStore.CommitChanges();

            return(entity);
        }
Пример #5
0
            public async Task Render(HttpRequest request, HttpResponse response, ASObject toRender)
            {
                response.ContentType = ConverterHelpers.GetBestMatch(_factory.MimeTypes, request.Headers["Accept"]);
                if (toRender["type"].Any(a => (string)a.Primitive == "Tombstone"))
                {
                    response.StatusCode = 410;
                }

                response.Headers.Add("Access-Control-Allow-Origin", "*");

                var depth = Math.Min(int.Parse(request.Query["depth"].FirstOrDefault() ?? "3"), 5);

                var unflattened = await _flattener.Unflatten(_entityStore, APEntity.From(toRender), depth);

                await response.WriteAsync(unflattened.Serialize(true).ToString());
            }
Пример #6
0
        public async Task <APEntity> GetEntity(string id, bool doRemote)
        {
            string fragment = null;
            string parsedId = null;

            // no fragment, so who cares
            if (!id.Contains("#"))
            {
                return(await Bypass.GetEntity(id, doRemote));
            }

            var split = id.Split(new char[] { '#' }, 2);

            fragment = split[1];
            parsedId = split[0];

            // try local get
            var entity = await Bypass.GetEntity(id, false);

            if (entity != null)
            {
                return(entity);
            }

            // doesn't exist, so try non-fragment
            entity = await Bypass.GetEntity(parsedId, false);

            if (entity != null && entity.IsOwner)
            {
                // exists, and I own it!
                var result = await _fakeEntityService.BuildFakeObject(entity, fragment);

                if (result == null)
                {
                    return(null);
                }
                return(APEntity.From(result, true));
            }

            if (!doRemote)
            {
                return(null);
            }

            return(await Bypass.GetEntity(id, true));
        }
Пример #7
0
        public async Task <IActionResult> RelevantEntities(RelevantObjectsModel model)
        {
            var user = User.FindFirstValue(JwtTokenSettings.ActorClaim);

            if (user == null)
            {
                return(Json(new List <ASObject>()));
            }

            var relevant = await _relevantEntities.FindRelevantObject(user, null, model.id);

            ASObject relevantObject = new ASObject();

            foreach (var item in relevant)
            {
                relevantObject["relevant"].Add(ASTerm.MakeSubObject(item.Data));
            }

            return(Content((await _flattener.Unflatten(_entityStore, APEntity.From(relevantObject), 5)).Serialize().ToString(), "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""));
        }
Пример #8
0
        public async Task <APEntity> Get(string url, IQueryCollection arguments, HttpContext context, APEntity existing)
        {
            var userId = _user.FindFirstValue("actor");
            var entity = existing ?? await _mainStore.GetEntity(url, false);

            if (entity == null)
            {
                return(null);
            }
            if (userId == null)
            {
                userId = await _verifier.Verify(url, context);
            }
            if (!await _authorizer.VerifyAccess(entity, userId))
            {
                var unauth = new ASObject();
                unauth.Id = "kroeg:unauthorized";
                unauth.Type.Add("kroeg:Unauthorized");

                return(APEntity.From(unauth));
            }
            if (entity.Type == "https://www.w3.org/ns/activitystreams#OrderedCollection" ||
                entity.Type == "https://www.w3.org/ns/activitystreams#Collection" ||
                entity.Type.StartsWith("_"))
            {
                if (entity.IsOwner && !entity.Data["totalItems"].Any())
                {
                    try {
                        return(APEntity.From(await _getCollection(entity, arguments), true));
                    } catch (FormatException) {
                        throw new InvalidOperationException("Invalid parameters!");
                    }
                }
                else
                {
                    return((await _mainStore.GetEntity(url + context.Request.QueryString.Value, true)) ?? entity);
                }
            }

            return(entity);
        }
Пример #9
0
        public async Task Invoke(HttpContext context, IServiceProvider serviceProvider, EntityData entityData, IEntityStore store)
        {
            var handler = ActivatorUtilities.CreateInstance <GetEntityHandler>(serviceProvider, context.User);

            if (entityData.RewriteRequestScheme)
            {
                context.Request.Scheme = "https";
            }

            var fullpath = $"{context.Request.Scheme}://{context.Request.Host}{context.Request.Path}";

            foreach (var converterFactory in _converters)
            {
                if (converterFactory.FileExtension != null && fullpath.EndsWith("." + converterFactory.FileExtension))
                {
                    fullpath = fullpath.Substring(0, fullpath.Length - 1 - converterFactory.FileExtension.Length);
                    context.Request.Headers.Remove("Accept");
                    context.Request.Headers.Add("Accept", converterFactory.RenderMimeType);
                    break;
                }
            }

            /* && ConverterHelpers.GetBestMatch(_converters[0].MimeTypes, context.Request.Headers["Accept"]) != null */
            if (context.Request.Method == "OPTIONS")
            {
                context.Response.StatusCode = 200;
                context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
                context.Response.Headers.Add("Access-Control-Allow-Headers", "Authorization");
                context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST");
                context.Response.Headers.Add("Access-Control-Allow-Origin", context.Request.Headers["Origin"]);
                context.Response.Headers.Add("Vary", "Origin");
                return;
            }

            Console.WriteLine(fullpath);
            foreach (var line in context.Request.Headers["Accept"])
            {
                Console.WriteLine($"---- {line}");
            }

            if (context.Request.Headers["Accept"].Contains("text/event-stream"))
            {
                await handler.EventStream(context, fullpath);

                return;
            }


            if (context.WebSockets.IsWebSocketRequest)
            {
                await handler.WebSocket(context, fullpath);

                return;
            }

            if (context.Request.Method == "POST" && context.Request.ContentType.StartsWith("multipart/form-data"))
            {
                context.Items.Add("fullPath", fullpath);
                context.Request.Path = "/settings/uploadMedia";
                await _next(context);

                return;
            }

            if (context.Request.QueryString.Value == "?hub")
            {
                context.Items.Add("fullPath", fullpath);
                context.Request.Path        = "/.well-known/hub";
                context.Request.QueryString = QueryString.Empty;
                await _next(context);

                return;
            }

            IConverter readConverter  = null;
            IConverter writeConverter = null;
            bool       needRead       = context.Request.Method == "POST";
            var        target         = fullpath;
            APEntity   targetEntity   = null;

            targetEntity = await store.GetEntity(target, false);

            if (needRead)
            {
                if (targetEntity?.Type == "_inbox")
                {
                    target = (string)targetEntity.Data["attributedTo"].Single().Primitive;
                }
            }

            if (targetEntity == null)
            {
                await _next(context);

                return;
            }


            foreach (var converterFactory in _converters)
            {
                bool worksForWrite = converterFactory.CanRender && ConverterHelpers.GetBestMatch(converterFactory.MimeTypes, context.Request.Headers["Accept"]) != null;
                bool worksForRead  = needRead && converterFactory.CanParse && ConverterHelpers.GetBestMatch(converterFactory.MimeTypes, context.Request.ContentType) != null;

                if (worksForRead && worksForWrite && readConverter == null && writeConverter == null)
                {
                    readConverter = writeConverter = converterFactory.Build(serviceProvider, target);
                    break;
                }

                if (worksForRead && readConverter == null)
                {
                    readConverter = converterFactory.Build(serviceProvider, target);
                }

                if (worksForWrite && writeConverter == null)
                {
                    writeConverter = converterFactory.Build(serviceProvider, target);
                }
            }

            ASObject data = null;

            if (readConverter != null)
            {
                data = await readConverter.Parse(context.Request.Body);
            }

            if (needRead && readConverter != null && writeConverter == null)
            {
                writeConverter = readConverter;
            }

            if (data == null && needRead && targetEntity != null)
            {
                context.Response.StatusCode = 415;
                await context.Response.WriteAsync("Unknown mime type " + context.Request.ContentType);

                return;
            }

            var arguments = context.Request.Query;

            try
            {
                if (context.Request.Method == "GET" || context.Request.Method == "HEAD" || context.Request.Method == "OPTIONS")
                {
                    data = await handler.Get(fullpath, arguments, context);
                }
                else if (context.Request.Method == "POST" && data != null)
                {
                    data = await handler.Post(context, fullpath, data);
                }
            }
            catch (UnauthorizedAccessException e)
            {
                context.Response.StatusCode = 403;
                await context.Response.WriteAsync(e.Message);
            }
            catch (InvalidOperationException e)
            {
                context.Response.StatusCode = 401;
                await context.Response.WriteAsync(e.Message);
            }

            if (context.Response.HasStarted)
            {
                return;
            }

            if (data != null)
            {
                if (context.Request.Method == "HEAD")
                {
                    context.Response.StatusCode = 200;
                    return;
                }

                if (writeConverter != null)
                {
                    await writeConverter.Render(context.Request, context.Response, data);
                }
                else if (context.Request.ContentType == "application/magic-envelope+xml")
                {
                    context.Response.StatusCode = 202;
                    await context.Response.WriteAsync("accepted");
                }
                else
                {
                    context.Request.Method  = "GET";
                    context.Request.Path    = "/render";
                    context.Items["object"] = APEntity.From(data);
                    await _next(context);
                }
                return;
            }

            if (!context.Response.HasStarted)
            {
                await _next(context);
            }
        }
Пример #10
0
        public async Task <IActionResult> DoBadgeToken([FromBody] BadgeTokenModel model)
        {
            var user = await _userManager.FindByNameAsync(model.Username);

            if (user == null)
            {
                user = new APUser {
                    UserName = model.Username, Email = model.Username + "@badge.local"
                };
                await _userManager.CreateAsync(user, model.Password);

                var uobj = model.Username;
                var name = model.Username;

                var obj = new ASObject();
                obj["type"].Add(new ASTerm("Person"));
                obj["preferredUsername"].Add(new ASTerm(name));
                obj["name"].Add(new ASTerm(name));

                var id = await _entityData.UriFor(_entityStore, obj);

                obj["id"].Add(new ASTerm(id));

                var inbox = await _newCollection("inbox", id);

                var outbox = await _newCollection("outbox", id);

                var following = await _newCollection("following", id);

                var followers = await _newCollection("followers", id);

                var likes = await _newCollection("likes", id);

                var blocks = await _newCollection("blocks", id);

                var blocked = await _newCollection("blocked", id);

                var blocksData = blocks.Data;
                blocksData["_blocked"].Add(new ASTerm(blocked.Id));
                blocks.Data = blocksData;

                obj["following"].Add(new ASTerm(following.Id));
                obj["followers"].Add(new ASTerm(followers.Id));
                obj["blocks"].Add(new ASTerm(blocks.Id));
                obj["likes"].Add(new ASTerm(likes.Id));
                obj["inbox"].Add(new ASTerm(inbox.Id));
                obj["outbox"].Add(new ASTerm(outbox.Id));

                var userEntity = await _entityStore.StoreEntity(APEntity.From(obj, true));

                await _entityStore.CommitChanges();

                _context.UserActorPermissions.Add(new UserActorPermission {
                    UserId = user.Id, ActorId = userEntity.Id, IsAdmin = true
                });
                await _context.SaveChangesAsync();
            }
            var u = await _signInManager.PasswordSignInAsync(model.Username, model.Password, false, false);

            if (!u.Succeeded)
            {
                return(Unauthorized());
            }

            var firstActor = await _context.UserActorPermissions.FirstOrDefaultAsync(a => a.User == user);

            var claims = new Claim[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, user.Id),
                new Claim(JwtTokenSettings.ActorClaim, firstActor.ActorId)
            };

            var jwt = new JwtSecurityToken(
                issuer: _tokenSettings.Issuer,
                audience: _tokenSettings.Audience,
                claims: claims,
                notBefore: DateTime.UtcNow,
                expires: DateTime.UtcNow.Add(TimeSpan.FromDays(7)),
                signingCredentials: _tokenSettings.Credentials
                );

            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

            return(Json(new BadgeTokenResponse {
                Actor = firstActor.ActorId, Token = encodedJwt
            }));
        }
Пример #11
0
        public override async Task <bool> Handle()
        {
            if (MainObject.Type != "https://www.w3.org/ns/activitystreams#Update" && MainObject.Type != "https://www.w3.org/ns/activitystreams#Delete")
            {
                return(true);
            }

            var activityData = MainObject.Data;

            var oldObject =
                await EntityStore.Bypass.GetEntity(activityData["object"].Single().Id, false);

            if (oldObject == null)
            {
                throw new InvalidOperationException("Cannot remove or update a non-existant object!");
            }

            if (!oldObject.IsOwner)
            {
                throw new InvalidOperationException("Cannot remove or update an object not on this server!");
            }

            var originatingCreate = (await _relevantEntities.FindRelevantObject("https://www.w3.org/ns/activitystreams#Create", oldObject.Id)).FirstOrDefault();

            if (originatingCreate.Data["actor"].Single().Id != Actor.Id)
            {
                throw new InvalidOperationException("Cannot remove or update objects that weren't made by you!");
            }

            if (MainObject.Type == "https://www.w3.org/ns/activitystreams#Update")
            {
                var newObject = await EntityStore.GetEntity(activityData["object"].Single().Id, false);

                if (newObject == oldObject)
                {
                    throw new InvalidOperationException("No new object passed!");
                }

                var data = oldObject.Data;
                foreach (var item in newObject.Data)
                {
                    // SequenceEqual ensures that clients doing full-object updates won't cause this exception on e.g. type, attributedTo, etc
                    if (UpdateBlacklist.Contains(item.Key) && (data[item.Key].Count != item.Value.Count || data[item.Key].Zip(item.Value, _isEqual).Any(a => !a)))
                    {
                        throw new InvalidOperationException($"Cannot update key {item.Key} as it's on the blacklist!");
                    }

                    data[item.Key].Clear();
                    data[item.Key].AddRange(item.Value);
                }

                data.Replace("updated", ASTerm.MakePrimitive(DateTime.Now.ToString("o")));

                newObject.Data = data;
                newObject.Type = oldObject.Type;
                await EntityStore.StoreEntity(newObject);
            }
            else
            {
                var newData = oldObject.Data;
                foreach (var kv in newData)
                {
                    if (!DeleteWhitelist.Contains(kv.Key))
                    {
                        kv.Value.Clear();
                    }
                }

                newData.Type.Clear();
                newData.Type.Add("https://www.w3.org/ns/activitystreams#Tombstone");
                newData.Replace("deleted", ASTerm.MakePrimitive(DateTime.Now.ToString("o")));

                var newObject = APEntity.From(newData, true);
                await EntityStore.StoreEntity(newObject);
            }

            return(true);
        }
Пример #12
0
        public async Task <APEntity> GetEntity(string id, bool doRemote)
        {
            if (id == "https://www.w3.org/ns/activitystreams#Public")
            {
                var aso = new ASObject();
                aso.Type.Add("https://www.w3.org/ns/activitystreams#Collection");
                aso.Id = "https://www.w3.org/ns/activitystreams#Public";

                var ent = APEntity.From(aso);
                return(ent);
            }
            try {
                if ((new Uri(id)).Host == "localhost")
                {
                    return(await Bypass.GetEntity(id, doRemote));
                }
            } catch (UriFormatException) { /* nom */ }

            APEntity entity = null;

            if (Bypass != null)
            {
                entity = await Bypass.GetEntity(id, doRemote);
            }
            if (entity != null && !entity.IsOwner && entity.Data.Type.Any(_collections.Contains) && doRemote)
            {
                entity = null;
            }

            if (entity?.Type == "_:LazyLoad" && !doRemote)
            {
                return(null);
            }
            if ((entity != null && entity.Type != "_:LazyLoad") || !doRemote)
            {
                return(entity);
            }

            var loadUrl = id;

            if (entity?.Type == "_:LazyLoad")
            {
                loadUrl = (string)entity.Data["href"].First().Primitive;
            }

            if (loadUrl.StartsWith("tag:"))
            {
                return(null);
            }

            var htc = new HttpClient();

            htc.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/activity+json; application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\", application/json, application/atom+xml, text/html");

            if (_context != null)
            {
                var signatureVerifier = _serviceProvider.GetRequiredService <SignatureVerifier>();
                var user = await Bypass.GetEntity(_context.User.FindFirstValue(JwtTokenSettings.ActorClaim), false);

                if (user != null)
                {
                    var jwt = await signatureVerifier.BuildJWS(user, id);

                    if (jwt != null)
                    {
                        htc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", jwt);
                    }
                }
            }

            HttpResponseMessage response = null;

            try
            {
                response = await htc.GetAsync(loadUrl);

                if (!response.IsSuccessStatusCode)
                {
                    response = await htc.GetAsync(loadUrl + ".atom"); // hack!

                    if (!response.IsSuccessStatusCode)
                    {
                        return(null);
                    }
                }
            }
            catch (TaskCanceledException)
            {
                return(null); // timeout
            }
            catch (HttpRequestException)
            {
                return(null);
            }

            var converters = new List <IConverterFactory> {
                new AS2ConverterFactory(), new AtomConverterFactory(false)
            };

tryAgain:
            ASObject data = null;

            foreach (var converter in converters)
            {
                if (converter.CanParse && ConverterHelpers.GetBestMatch(converter.MimeTypes, response.Content.Headers.ContentType.ToString()) != null)
                {
                    data = await converter.Build(_serviceProvider, null).Parse(await response.Content.ReadAsStreamAsync());

                    break;
                }
            }

            if (data == null)
            {
                if (response.Headers.Contains("Link"))
                {
                    var split = string.Join(", ", response.Headers.GetValues("Link")).Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (var spl in split)
                    {
                        var args = spl.Split(';').Select(a => a.Trim()).ToArray();
                        if (args.Contains("rel=\"alternate\"") && args.Contains("type=\"application/atom+xml\""))
                        {
                            response = await htc.GetAsync(args[0].Substring(1, args[0].Length - 2));

                            goto tryAgain;
                        }
                    }
                }

                try
                {
                    var links = (await response.Content.ReadAsStringAsync()).Split('\n');
                    var alt   = links.FirstOrDefault(a => a.Contains("application/atom+xml") && a.Contains("alternate"));
                    if (alt == null)
                    {
                        return(null);
                    }
                    var l = XDocument.Parse(alt + "</link>").Root;
                    if (l.Attribute(XNamespace.None + "type")?.Value == "application/atom+xml")
                    {
                        response = await htc.GetAsync(l.Attribute(XNamespace.None + "href")?.Value);

                        goto tryAgain;
                    }
                }
                catch (Exception) { }

                return(null);
            }

            // forces out the old lazy load, if used
            await _entityFlattener.FlattenAndStore(Bypass, data, false);

            return(await Bypass.GetEntity(id, true));
        }
Пример #13
0
        private async Task <ASObject> _unflatten(IEntityStore store, APEntity entity, int depth, IDictionary <string, APEntity> alreadyMapped, bool remote)
        {
            if (depth == 0)
            {
                return(entity.Data);
            }

            var @object = entity.Data;

            if (_configuration.IsActor(@object) && entity.IsOwner)
            {
                @object = _getEndpoints(entity);
            }

            var myid = @object.Id;

            if (myid != null)
            {
                alreadyMapped[myid] = entity;
            }

            @object["bto"].Clear();
            @object["bcc"].Clear();

            foreach (var kv in @object)
            {
                foreach (var value in kv.Value)
                {
                    if (value.SubObject != null)
                    {
                        value.SubObject = await _unflatten(store, APEntity.From(value.SubObject), depth - 1, alreadyMapped, remote);
                    }
                    if (value.Id == null)
                    {
                        continue;
                    }
                    if (_mayNotUnflatten.Contains(kv.Key) && (!entity.IsOwner || !UnflattenIfOwner.Contains(kv.Key)))
                    {
                        continue;
                    }
                    var id = value.Id;

                    if (alreadyMapped.ContainsKey(id) && (depth != 3 || kv.Key != "https://www.w3.org/ns/activitystreams#object"))
                    {
                        continue;
                    }

                    var obj = await store.GetEntity(id, false);

                    if (obj == null || _avoidFlatteningTypes.Contains(obj.Type) || obj.Type.StartsWith("_") || (!remote && !obj.IsOwner))
                    {
                        continue;
                    }
                    value.Primitive = null;
                    value.Id        = null;
                    value.SubObject = await _unflatten(store, obj, depth - 1, alreadyMapped, remote);
                }
            }

            return(@object);
        }
Пример #14
0
        public async Task <APEntity> GetEntity(string id, bool doRemote)
        {
            if (id == "https://www.w3.org/ns/activitystreams#Public")
            {
                var aso = new ASObject();
                aso.Type.Add("https://www.w3.org/ns/activitystreams#Collection");
                aso.Id = "https://www.w3.org/ns/activitystreams#Public";

                var ent = APEntity.From(aso);
                return(ent);
            }
            string origin = id;

            try {
                var uri = new Uri(id);
                if (uri.Host == "localhost")
                {
                    return(await Bypass.GetEntity(id, doRemote));
                }
                origin = uri.GetLeftPart(UriPartial.Authority);
            } catch (UriFormatException) { /* nom */ }

            APEntity entity = null;

            if (Bypass != null)
            {
                entity = await Bypass.GetEntity(id, doRemote);
            }

/*            if (entity == null)
 *          {
 *              var possibilities = (await _getRE().Query(new RelevantEntitiesService.ContainsAnyStatement("https://www.w3.org/ns/activitystreams#url") { id })).Where(a => Uri.IsWellFormedUriString(a.Id, UriKind.Absolute) && (new Uri(a.Id)).GetLeftPart(UriPartial.Authority) == origin).ToList();
 *              if (possibilities.Count == 1) entity = possibilities.First();
 *          }*/
            if (entity != null && !entity.IsOwner && entity.Data.Type.Any(_collections.Contains) && doRemote)
            {
                entity = null;
            }
            if (entity != null || !doRemote)
            {
                return(entity);
            }

            var htc     = new HttpClient();
            var request = new HttpRequestMessage(HttpMethod.Get, id);

            request.Headers.TryAddWithoutValidation("Accept", "application/activity+json; application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\", application/json, text/html");

            if (_context != null)
            {
                var signatureVerifier = _serviceProvider.GetRequiredService <SignatureVerifier>();
                var user = await Bypass.GetEntity(_context.User.FindFirstValue("actor"), false);

                if (user != null)
                {
                    var jwt = await signatureVerifier.BuildJWS(user, id);

                    if (jwt != null)
                    {
                        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", jwt);
                    }
                    request.Headers.TryAddWithoutValidation("Signature", await _keyService.BuildHTTPSignature(user.Id, request));
                }
            }

            HttpResponseMessage response = null;

            try
            {
                response = await htc.SendAsync(request);
            }
            catch (TaskCanceledException)
            {
                return(null); // timeout
            }
            catch (HttpRequestException)
            {
                return(null);
            }

            ASObject data = null;
            await response.Content.LoadIntoBufferAsync();

            foreach (var converter in ServerConfig.Converters)
            {
                if (converter.CanParse && ConverterHelpers.GetBestMatch(converter.MimeTypes, response.Content.Headers.ContentType.ToString()) != null)
                {
                    try {
                        data = await converter.Build(_serviceProvider, null).Parse(await response.Content.ReadAsStreamAsync());

                        break;
                    } catch (NullReferenceException e) { Console.WriteLine(e); /* nom */ }
                }
            }

            if (data == null)
            {
                return(null);
            }

            // forces out the old lazy load, if used
            await _entityFlattener.FlattenAndStore(Bypass, data, false);

            return(await Bypass.GetEntity(id, true));
        }