core icon indicating copy to clipboard operation
core copied to clipboard

Getting the community scope

Open luap42 opened this issue 6 years ago • 15 comments

I think we need to have this before being able to work on posts and users:

https://github.com/codidact/core/projects/1#card-31579420

A system, which parses the URL, gets the community ID from it, verifies, whether that is in the DB and if it is, loads some information (name, tagline) into memory and passes that to the further controllers and views.

I'd do it, but I don't have enough knowledge in ASP.NET Core, so I'd like to ask for volunteers doing it. :)

Please reply here, when you can do this and I'll assign this task to you.

luap42 avatar Jan 18 '20 23:01 luap42

I can take a stab at it. Are you envisioning this code running before every controller?

benjamin-allen avatar Jan 18 '20 23:01 benjamin-allen

https://github.com/codidact/core/issues/21 is blocking this

misha130 avatar Jan 18 '20 23:01 misha130

Most. There'll be some "neutral" pages, such as authentication, administration and community management (add, edit, remove), though.

luap42 avatar Jan 18 '20 23:01 luap42

Most. There'll be some "neutral" pages, such as authentication, administration and community management (add, edit, remove), though.

That should be possible. I'll make a base controller class that performs the URL parsing you describe. Controllers that don't want that functionality just don't inherit from that base class.

#21 is blocking this

I'm not sure why that's the case. The way I'm reading it is that this issue relates to only community data and that issue is concerned with user auth. Am I missing something?

benjamin-allen avatar Jan 18 '20 23:01 benjamin-allen

I'm not sure why that's the case. The way I'm reading it is that this issue relates to only community data and that issue is concerned with user auth. Am I missing something?

I misread the issue at hand. I think we are talking about only displaying in part of a page the community tagline and name. Just need to create a PartialView/View for this task that checks the URL and displays the community based on that. You don't need to create base controllers or anything like that.

misha130 avatar Jan 18 '20 23:01 misha130

If my understanding is correct, then the consensus was to use subdomains. This would require a bit more configuration that is currently documented in README.md.

IIS Express (that is the server thingy that Visual Studio uses) only binds to e.g. https://localhost:8080 by default, but not to e.g. https://foo.localhost:8080. Thus to develop and test this service scope middleware or whatever, a more sophisticated setup is required.

I've tried to come up with something myself, and found this stack overflow answer which seemed promising, but didn't work for me.

Does anyone know how to configure this on Windows?


I've managed to set this up on Linux as follows:

  1. Edit /etc/hosts and add:

    127.0.0.2    codidact.local
    127.0.0.2    foo.codidact.local
    127.0.0.2    bar.codidact.local
    
  2. Edit src/WebUI/Properties/launchSettings.json as follows:

    {
      "profiles": {
        "WebUI": {
          "commandName": "Project",
          "launchBrowser": true,
          "applicationUrl": "https://codidact.local",
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        }
      }
    }
    

asynts avatar Jan 19 '20 15:01 asynts

My recommendation (I think I made it clear in the forum) is that the Codidact software should support both methods, while the Codidact primary instance should (normally, see exception) use subdomains:

  • Use DNS + extra certificates (because Let's Encrypt doesn't do wildcard subdomains and Let's Encrypt is the only practical certificate at this point in time for a low-budget operation - others cost a lot (relatively speaking) and self-signed is no longer a practical option due to browser warnings), etc. to support subdomains.
  • Add a "glue" piece somewhere (middleware or whatever) that maps a.example.com to example.com/community/a (where example.com is the primary instance domain - e.g., codidact.com) and community is an instance-configurable pseudo-directory name (also called 'X' in one of the prior discussions). Technically speaking 'a.' and '/a' could have different values, but easiest (for a few reasons) to require them to be the same - e.g., writing.codidact.com and codidact.com/community/writing)
  • The application software only very minimally needs to even know about the existence of the subdomain setting. Essentially, it has a few configuration items:
    • instance domain - e.g., codidact.com
    • community separator - e.g., community or site or X (note, this is needed to avoid naming conflicts with instance-level URLs - e.g., otherwise if you had a site for SysAdmins and called it "admin" then codidact.com/admin would be ambiguous for "admin" community vs. "admin" page for the instance.
    • per-community: community identifier (limited to valid domain name alphanumeric/etc.) - e.g., writing, diy, judaism, rc, etc.)
    • per-community: use_subdomain - This is necessary because even a Codidact instance that mostly uses subdomains may need this. For example, when setting up a new community, this allows the setup to be done prior to setting up DNS for the new subdomain. If an instance has an Area 51, there might a rule (policy only, not technical) to only set up a subdomain once a community has reached a certain stage (Beta?). etc.

The code should care very little about this. It effectively becomes a "filter" of sorts on input and output - like Apache RewriteRule, except managed to a certain degree (I know it can be done, just not exactly how) within the software.

manassehkatz avatar Jan 19 '20 15:01 manassehkatz

Do we really need to implement anything at the application level to manage this? AFAIK it's usually done at the server level (URL Rewrite in Apache, IIS or whatever else).

It wasn't long ago that I refactored out some old URL rewriting code from a legacy .NET web application and ported all rules to IIS instead. Worked just as well, if not better. Very nice to maintain and everyone was happy.

ranolfi avatar Jan 21 '20 06:01 ranolfi

Do we really need to implement anything at the application level to manage this? AFAIK it's usually done at the server level (URL Rewrite in Apache, IIS or whatever else).

The rewriting should only happen if some configuration option is turned on.

If we put this option into the server configuration, it might be necessary to get that information in the application too for e.g. redirects. This would mean redundancy or some common configuration file which seems difficult.


I am also unsure how much the server configuration has to offer, there is also a URL Rewriting Middleware in ASP.NET integrated, but it's not powerful enough to do this rewriting (at least to my knowledge).

asynts avatar Jan 21 '20 08:01 asynts

The rewriting should only happen if some configuration option is turned on.

Is this requirement specified somewhere? I must have missed it (and, in principle, don't agree with).

If we put this option into the server configuration, it might be necessary to get that information in the application too for e.g. redirects. This would mean redundancy or some common configuration file which seems difficult.

In terms of KISS, I very much doubt it will be more difficult than implementing and specially maintaining the middleware in the long term.

I am also unsure how much the server configuration has to offer

A lot! More than enough for the current needs (that I'm aware of).

I maintain that the server configuration is the right place to do this. When there is a requirement for localizing URLs, a middleware might be considered for that specific purpose.

ranolfi avatar Jan 21 '20 17:01 ranolfi

~~Your formatting is broken, you have to put a newline after a quote, otherwise it is lazily continued.~~

Is this requirement specified somewhere? I must have missed it (and, in principle, don't agree with).

There was some discussion here, and some stuff further down the thread, leading to this post.

The big plus is that we can use subdomains on https://codidact.com while allowing other instances to use the path schema.

A lot! More than enough for the current needs (that I'm aware of).

I've never done rewriting in a server configuration, maybe one simple regex but nothing more. Maybe someone could provide an example of how this could be done. I got the suspicion that this results in one mega regex that is completely unreadable.

asynts avatar Jan 21 '20 17:01 asynts

Your formatting is broken, you have to put a newline after a quote, otherwise it is lazily continued.

Got it, ty.

Let me follow up on those and come back later to comment. :)

ranolfi avatar Jan 21 '20 18:01 ranolfi

@ranolfi I changed my position on this topic. Don't get me wrong, on the long term doing it with configuration options in C# would is preferable in my opinion, however, we are creating a minimum viable product, thus simpler is better.

I've come up with the following configuration for Nginx:

events {
}

http {
    server {
        listen 80;
        server_name localhost;

        location / {
            proxy_pass https://127.1.0.1:8080/;
        }
    }

    server {
        listen 80;
        server_name ~^(?<community>\w+)\.localhost$;

        location / {
            proxy_pass https://127.1.0.1:8080/community/$community$uri;
        }
    }
}

I am sure something similar can be done in IIS too, but I think someone with Windows knowledge should address that. This can also be done at a later date, because we'd use the path schema during development anyway.

I'll close #23 and salvage some of the configuration stuff in a separate pull request, unless anyone objects.

asynts avatar Jan 22 '20 13:01 asynts

I edited the configuration to use encryption and added the redirect:

events {
}

http {
    ssl_certificate certs/cert.pem;
    ssl_certificate_key certs/key.pem;

    server {
        listen 443 ssl;
        server_name localhost;

        location ~^/community/(?<community>\w+)(?<path>.*?)$ {
            return 301 https://$community.localhost$path;
        }

        location / {
            proxy_pass https://127.1.0.1:8080/;
        }
    }

    server {
        listen 443 ssl;
        server_name ~^(?<community>\w+)\.localhost$;

        location / {
            proxy_pass https://127.1.0.1:8080/community/$community$request_uri;
        }
    }
}

asynts avatar Jan 22 '20 18:01 asynts

For anyone that wants to actually do it here is what you need to do:

! PSUEDO CODE WARNING ! Add a routing option in the startup, example:

       app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "/community/{community}/{controller}/{action}/{id?}");
            });

Add the global filter if appropriate:

                    var routerData  = ....
                    var isCommunityable= entity.FindProperty(nameof(ICommunityable.CommunityId));
                    var parameter = Expression.Parameter(entity.ClrType, "p");
                    var equalExpression = Expression.Equal(
                            Expression.Property(parameter, isCommunityable.PropertyInfo),
                            Expression.Constant(routerData["Community"])
                        );
                    var filter = Expression.Lambda(equalExpression, parameter);
                    entity.SetQueryFilter(filter);

misha130 avatar Jan 29 '20 20:01 misha130