Conversation
✅ Deploy Preview for authentik-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
✅ Deploy Preview for authentik-storybook ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
| @@ -0,0 +1,253 @@ | |||
| """List of all ISO-3166-1 alpha-2 country codes""" | |||
There was a problem hiding this comment.
An alternative to this is adding a dependency like iso3166 or django-countries.
Since ISO 3166 changes infrequently, I erred on the side of fewer dependencies.
There was a problem hiding this comment.
I don't mind adding a dependency for this, especially since we should add gettext calls to all these humanized names, and some dependency might have full translation for all the country names already
There was a problem hiding this comment.
Added django-countries. I'm unfamiliar with drf-spectacular and needed a bit of dark magic to make it work. If there's an easier way, let me know.
| .fetchObjects=${async () => { | ||
| return [ | ||
| { name: "allow", label: "Allow" }, | ||
| { name: "block", label: "Block" }, | ||
| ]; | ||
| }} |
There was a problem hiding this comment.
The use of an async function here for a static list of 2 is probably the worst part of this PR. I'll look to improve this.
There was a problem hiding this comment.
Yeah, search select is specifically for complex objects that have subtitles and complex interactions. Consider using a Switch for a boolean choice, or if the ergonomics are bad (it's sometimes hard to word), a Radio.
| if (data.asnMode?.toString() === "") { | ||
| data.asnMode = "block"; | ||
| } | ||
|
|
||
| if (data.asnList?.toString() === "") { | ||
| data.asnList = []; | ||
| } else { | ||
| data.asnList = data.asnList.split(",").map(Number); | ||
| } | ||
|
|
||
| if (data.countryMode?.toString() === "") { | ||
| data.countryMode = "block"; | ||
| } | ||
|
|
||
| if (data.countryList?.toString() === "") { | ||
| data.countryList = []; | ||
| } else { | ||
| data.countryList = data.countryList.split(","); | ||
| } |
There was a problem hiding this comment.
Defaults should be part of the back-end. Also some botched types here.
There was a problem hiding this comment.
Yeah, I'm looking at this and thinking that I'm seeing a lot of mixed messages compared to the types generated from the schema. You shouldn't have to be splitting anything; the API should be giving those to you, and the TypeScript definitions in GeoIPPolicy.ts say getting strings where arrays are expected shouldn't be possible). This function is probably the wrong place to be doing this.
If you're crafting a blob to send, start with a default and overwrite it with what you get from the form instead.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #10454 +/- ##
==========================================
+ Coverage 92.51% 92.56% +0.04%
==========================================
Files 720 727 +7
Lines 35254 35418 +164
==========================================
+ Hits 32615 32784 +169
+ Misses 2639 2634 -5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
kensternberg-authentik
left a comment
There was a problem hiding this comment.
Overall, the Typescript is really promising! Good work. There's a bit of rough edges, and it's obviously not done, but it's getting there.
| if (data.asnMode?.toString() === "") { | ||
| data.asnMode = "block"; | ||
| } | ||
|
|
||
| if (data.asnList?.toString() === "") { | ||
| data.asnList = []; | ||
| } else { | ||
| data.asnList = data.asnList.split(",").map(Number); | ||
| } | ||
|
|
||
| if (data.countryMode?.toString() === "") { | ||
| data.countryMode = "block"; | ||
| } | ||
|
|
||
| if (data.countryList?.toString() === "") { | ||
| data.countryList = []; | ||
| } else { | ||
| data.countryList = data.countryList.split(","); | ||
| } |
There was a problem hiding this comment.
Yeah, I'm looking at this and thinking that I'm seeing a lot of mixed messages compared to the types generated from the schema. You shouldn't have to be splitting anything; the API should be giving those to you, and the TypeScript definitions in GeoIPPolicy.ts say getting strings where arrays are expected shouldn't be possible). This function is probably the wrong place to be doing this.
If you're crafting a blob to send, start with a default and overwrite it with what you get from the form instead.
| )} | ||
| </span> | ||
| <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name"> | ||
| <input |
There was a problem hiding this comment.
?required=${true} isn't needed here (and I really need to break @BeryJu of the habit). required is all that's needed. You only need the ? syntax for boolean attributes if the attribute can be changed by client code, for example:
?hidden=${!this.open}
There was a problem hiding this comment.
Yeah we should probably just rip off the bandaid and change all of those wrong attributes to the correct ones
| <input | ||
| class="pf-c-switch__input" | ||
| type="checkbox" | ||
| ?checked=${first(this.instance?.executionLogging, false)} |
There was a problem hiding this comment.
first is a leftover from earlier iterations of TypeScript. Please use the idiom:
${this.instance?.executionLogging ?? false}
Although in this case I suspect you could just use the boolean forcing expression: ${!!this.instance?.executionLogging}
| .fetchObjects=${async () => { | ||
| return [ | ||
| { name: "allow", label: "Allow" }, | ||
| { name: "block", label: "Block" }, | ||
| ]; | ||
| }} |
There was a problem hiding this comment.
Yeah, search select is specifically for complex objects that have subtitles and complex interactions. Consider using a Switch for a boolean choice, or if the ergonomics are bad (it's sometimes hard to word), a Radio.
| </p> | ||
| </ak-form-element-horizontal> | ||
| <ak-form-element-horizontal label=${msg("Country Mode")} name="countryMode"> | ||
| <ak-search-select |
There was a problem hiding this comment.
... and given that you've done it twice, break it out into a variable or something so you'll only have to change it once if the rules change. :-)
| <ak-form-element-horizontal label=${msg("Country List")} name="countryList"> | ||
| <input | ||
| type="text" | ||
| value="${ifDefined(this.instance?.countryList || "")}" |
There was a problem hiding this comment.
Since you've provided a not-undefined value for falsity, ifDefined(this.instance?.countryList || "") will always be defined. You can replace that whole expression with this.instance?.countryList ?? ""
The exceptions raised here are `PolicyException`s to let admins bypass an execution failure.
whoops
| asn = asn_data.get("asn") if asn_data else None | ||
| country = geoip_data.get("country") if geoip_data else None | ||
|
|
||
| if not asn: |
There was a problem hiding this comment.
both of these cases can also happen if ASN/GeoIP is not configured (pretty rare since we include the DB by default, but it is possible to disable)
There was a problem hiding this comment.
(We've had a bit of an offline discussion, but no resolution to this.) I kept the behavior for now, this way we don't leak information on whether a GeoIP database is available.
Let me know if you don't agree.
authentik/policies/geoip/models.py
Outdated
| from authentik.policies.types import PolicyRequest, PolicyResult | ||
|
|
||
|
|
||
| class GeoIPPolicyMode(models.TextChoices): |
There was a problem hiding this comment.
Usually we don't have this as a setting directly on the policy but instead use the negate flag on the policy binding to inverse the effects. For this case I'm a bit on the fence as you can do two different things in the same policy with it
There was a problem hiding this comment.
Oh, I didn't notice that flag! That makes everything so much simpler. I'm not on the fence about it at all 😄
Then there's no need for *_mode and this model will just have 2 fields: asn_list and country_list. Same as Event Matcher Policy for least astonishment:
If any of the configured values match, the policy passes.
| } | ||
|
|
||
| async send(data: GeoIPPolicy): Promise<GeoIPPolicy> { | ||
| if (data.asnMode?.toString() === "") { |
There was a problem hiding this comment.
This is somewhat of a downside of the generated API client, but this value here should never be undefined so this if shouldn't be required
There was a problem hiding this comment.
Combined with the check below (which is still needed because "".split(",") is non-empty)
| @@ -0,0 +1,253 @@ | |||
| """List of all ISO-3166-1 alpha-2 country codes""" | |||
There was a problem hiding this comment.
I don't mind adding a dependency for this, especially since we should add gettext calls to all these humanized names, and some dependency might have full translation for all the country names already
1ea7057 to
b4bb967
Compare
Use the policy binding's `negate` option instead
`ak-dual-select-provider` can handle unpaginated data
b4bb967 to
d76003b
Compare
d76003b to
9078588
Compare
authentik/policies/geoip/api.py
Outdated
| COUNTRY_SCHEMA = { | ||
| "code": CountryField(), | ||
| "name": serializers.CharField(), | ||
| } |
There was a problem hiding this comment.
This is an exact copy of authentik/policies/geoip/views:11-14. Weirdly, importing it from there results in gen-build silently failing to generate this part of the schema. I'm out of my depth here, so I just duplicated these 4 lines.
There was a problem hiding this comment.
That is very odd but also doesn't surprise me
authentik/policies/geoip/urls.py
Outdated
|
|
||
| api_urlpatterns = [ | ||
| ("policies/geoip", GeoIPPolicyViewSet), | ||
| path("iso-3166/", ISO3166View.as_view(), name="iso-3166-view"), |
There was a problem hiding this comment.
There may or may not be a better place for this.
There was a problem hiding this comment.
I'd usually prefer to do it as policies/geoip/iso3166/, but obviously that could potentially cause errors due to that path also having the primary key...as a fallback I'd go with policies/geoip_iso3166/ I guess?
| if (data.asns?.toString() === "") { | ||
| data.asns = []; | ||
| } else { | ||
| data.asns = (data.asns as unknown as string).split(",").map(Number); | ||
| } |
There was a problem hiding this comment.
The actual type of data slightly differs from GeoIPPolicy.
I haven't found an easy way to reconcile them (the "proper" way is probably building a component on top of <input> to run a transforming function when its value is read), so I opted for the type assertion.
There was a problem hiding this comment.
You could just write:
data.asns = (data.asns?.toString() ?? "") === "" ? [] : (data.asns as unknown as string).split(",").map(Number);
... but it feels like the toString() thing is a crutch you're using, and I'm not sure why.
| .provider=${(page: number, search?: string): Promise<DataProvision> => { | ||
| return new Iso3166Api(DEFAULT_CONFIG) | ||
| .iso3166List() | ||
| .then((results) => { | ||
| if (!search) return results; | ||
| return results.filter((result) => | ||
| result.name | ||
| .toLowerCase() | ||
| .includes(search.toLowerCase()), | ||
| ); | ||
| }) | ||
| .then((results) => { | ||
| return { | ||
| options: results.map((country) => [ | ||
| country.code, | ||
| country.name, | ||
| ]), | ||
| }; | ||
| }); | ||
| }} |
There was a problem hiding this comment.
This currently hits the API for no reason every time there's a change in search. I'll add an optimization.
There was a problem hiding this comment.
Yeah this is due to the assumption that the search filtering is done in the backend and not that the endpoint will return everything
There was a problem hiding this comment.
You can write a provider that caches the result, and then provides the (locally reduces) results locally. Or you could look at ak-dual-select, which doesn't use the API; you could fetch the results once and then use the Web Component directly without having to go through the Element's API layer.
| .provider=${(page: number, search?: string): Promise<DataProvision> => { | ||
| return new Iso3166Api(DEFAULT_CONFIG) | ||
| .iso3166List() | ||
| .then((results) => { | ||
| if (!search) return results; | ||
| return results.filter((result) => | ||
| result.name | ||
| .toLowerCase() | ||
| .includes(search.toLowerCase()), | ||
| ); | ||
| }) | ||
| .then((results) => { | ||
| return { | ||
| options: results.map((country) => [ | ||
| country.code, | ||
| country.name, | ||
| ]), | ||
| }; | ||
| }); | ||
| }} |
There was a problem hiding this comment.
Yeah this is due to the assumption that the search filtering is done in the backend and not that the endpoint will return everything
| )} | ||
| </span> | ||
| <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name"> | ||
| <input |
There was a problem hiding this comment.
Yeah we should probably just rip off the bandaid and change all of those wrong attributes to the correct ones
authentik/policies/geoip/api.py
Outdated
| COUNTRY_SCHEMA = { | ||
| "code": CountryField(), | ||
| "name": serializers.CharField(), | ||
| } |
There was a problem hiding this comment.
That is very odd but also doesn't surprise me
authentik/policies/geoip/urls.py
Outdated
|
|
||
| api_urlpatterns = [ | ||
| ("policies/geoip", GeoIPPolicyViewSet), | ||
| path("iso-3166/", ISO3166View.as_view(), name="iso-3166-view"), |
There was a problem hiding this comment.
I'd usually prefer to do it as policies/geoip/iso3166/, but obviously that could potentially cause errors due to that path also having the primary key...as a fallback I'd go with policies/geoip_iso3166/ I guess?
The automatically generated APIs can't seem to handle `CountryField`, so I'll have to do this by hand too.
tanberry
left a comment
There was a problem hiding this comment.
Yay we have a GeoIP policy! Thanks so much for updating the docs. A few nits, mostly about capitalization... I see that this whole page does not follow our Style Guide about using sentence-case capitalization for headers/titles. ("GeoIP policy" not "GeoIP Policy".)
website/docs/core/geoip.mdx
Outdated
| # GeoIP | ||
|
|
||
| authentik supports GeoIP to add additional information to login/authorization/enrollment requests, and make policy decisions based on the lookup result. | ||
| authentik supports GeoIP to add additional information to login/authorization/enrollment requests. Additionally, a [GeoIP Policy](../policies/#geoip-policy) may be used to make policy decisions based on the lookup result. |
There was a problem hiding this comment.
| authentik supports GeoIP to add additional information to login/authorization/enrollment requests. Additionally, a [GeoIP Policy](../policies/#geoip-policy) may be used to make policy decisions based on the lookup result. | |
| authentik supports GeoIP to add additional information to login/authorization/enrollment requests. Additionally, a [GeoIP policy](../policies/#geoip-policy) can be used to make policy decisions based on the lookup result. |
|
|
||
| ```python | ||
| return context["geoip"]["country"] == "US" | ||
| return context["geoip"]["continent"] == "EU" |
There was a problem hiding this comment.
Is there an example where we can use both "country" and "continent"? Will most people know the parms? (I wouldn't have know that continent was an option.)
There was a problem hiding this comment.
It's possible, e.g. countries in Eurovision famously include Australia 🙂
return context["geoip"]["continent"] == "EU" or context["geoip"]["country"] == "AU"
but I believe this might be more confusing than just keeping it simple. The params are listed right above, so I don't think "new confusion" arises from changing this.
(I changed this just to suggest something other that what could be done with a GeoIP policy.)
website/docs/policies/expression.mdx
Outdated
| - `asn`: ASN dictionary. The follow fields are available: | ||
|
|
||
| :::info | ||
| For basic ASN matching, consider using a [GeoIP Policy](index.md#geoip-policy). |
There was a problem hiding this comment.
| For basic ASN matching, consider using a [GeoIP Policy](index.md#geoip-policy). | |
| For basic ASN matching, consider using a [GeoIP policy](index.md#geoip-policy). |
website/docs/policies/expression.mdx
Outdated
| - `geoip`: GeoIP dictionary. The following fields are available: | ||
|
|
||
| :::info | ||
| For basic country matching, consider using a [GeoIP Policy](index.md#geoip-policy). |
There was a problem hiding this comment.
| For basic country matching, consider using a [GeoIP Policy](index.md#geoip-policy). | |
| For basic country matching, consider using a [GeoIP policy](index.md#geoip-policy). |
website/docs/policies/index.md
Outdated
|
|
||
| See [Expression Policy](expression.mdx). | ||
|
|
||
| ## GeoIP Policy |
There was a problem hiding this comment.
| ## GeoIP Policy | |
| ## GeoIP policy |
There was a problem hiding this comment.
Hmmm I see that all of the other headers/titles for policies also capitalize "Policy". Our style is to use sentence style capitalization for headings... do you mind also changing all of the other headings to on this age to use a lower-case "p" on "policies"? :-)
website/docs/policies/index.md
Outdated
|
|
||
| ## GeoIP Policy | ||
|
|
||
| Use this policy for simple GeoIP lookups, such as country or ASN matching. (For a more advanced GeoIP lookup, use an [Expression Policy](expression.mdx).) |
There was a problem hiding this comment.
| Use this policy for simple GeoIP lookups, such as country or ASN matching. (For a more advanced GeoIP lookup, use an [Expression Policy](expression.mdx).) | |
| Use this policy for simple GeoIP lookups, such as country or ASN matching. (For a more advanced GeoIP lookup, use an [Expression policy](expression.mdx).) |
tanberry
left a comment
There was a problem hiding this comment.
Thanks so much for updating the Docs!
add review suggestions from @tanberry
986f1bd to
18f3367
Compare
kensternberg-authentik
left a comment
There was a problem hiding this comment.
Overall, this looks quite good. I have a few quibbles about syntax and idioms, but they're more of a "I wouldn't do it this way, but it works and it's not wrong, so let's go with it." If you want to tighten up a few of the wordier expressions, feel free, but otherwise I'm happy.
| if (data.asns?.toString() === "") { | ||
| data.asns = []; | ||
| } else { | ||
| data.asns = (data.asns as unknown as string).split(",").map(Number); | ||
| } |
There was a problem hiding this comment.
You could just write:
data.asns = (data.asns?.toString() ?? "") === "" ? [] : (data.asns as unknown as string).split(",").map(Number);
... but it feels like the toString() thing is a crutch you're using, and I'm not sure why.
| .provider=${(page: number, search?: string): Promise<DataProvision> => { | ||
| return new Iso3166Api(DEFAULT_CONFIG) | ||
| .iso3166List() | ||
| .then((results) => { | ||
| if (!search) return results; | ||
| return results.filter((result) => | ||
| result.name | ||
| .toLowerCase() | ||
| .includes(search.toLowerCase()), | ||
| ); | ||
| }) | ||
| .then((results) => { | ||
| return { | ||
| options: results.map((country) => [ | ||
| country.code, | ||
| country.name, | ||
| ]), | ||
| }; | ||
| }); | ||
| }} |
There was a problem hiding this comment.
You can write a provider that caches the result, and then provides the (locally reduces) results locally. Or you could look at ak-dual-select, which doesn't use the API; you could fetch the results once and then use the Web Component directly without having to go through the Element's API layer.
It is now as declarative as I could make it.
rissson
left a comment
There was a problem hiding this comment.
Some nitpicking, but otherwise looks quite good!
* main: (473 commits) blueprints: handle model referencing non-existent app/model (#10796) website/docs: add more content about flows (#10527) brands: add OIDC webfinger support (#10400) web/admin: fix selectable card colour in dark theme (#10794) web: bump API Client version (#10793) policies: add GeoIP policy (#10454) core: bump debugpy from 1.8.3 to 1.8.5 (#10781) web: bump @sentry/browser from 8.22.0 to 8.23.0 in /web in the sentry group across 1 directory (#10782) website: bump postcss from 8.4.40 to 8.4.41 in /website (#10783) web: bump the wdio group across 2 directories with 4 updates (#10785) web: bump @lit/localize from 0.12.1 to 0.12.2 in /web (#10786) web: bump @floating-ui/dom from 1.6.8 to 1.6.9 in /web (#10787) web: bump @lit/localize-tools from 0.7.2 to 0.8.0 in /web (#10788) web: bump lit from 3.1.4 to 3.2.0 in /web (#10789) core: bump goauthentik.io/api/v3 from 3.2024062.2 to 3.2024063.1 (#10790) web: bump API Client version (#10779) release: 2024.6.3 website/docs: prepare 2024.6.3 release notes (#10775) website/scripts: updated readme, added docsmg.env file (#10710) web: bump API Client version (#10777) ...
* main: (702 commits) blueprints: handle model referencing non-existent app/model (#10796) website/docs: add more content about flows (#10527) brands: add OIDC webfinger support (#10400) web/admin: fix selectable card colour in dark theme (#10794) web: bump API Client version (#10793) policies: add GeoIP policy (#10454) core: bump debugpy from 1.8.3 to 1.8.5 (#10781) web: bump @sentry/browser from 8.22.0 to 8.23.0 in /web in the sentry group across 1 directory (#10782) website: bump postcss from 8.4.40 to 8.4.41 in /website (#10783) web: bump the wdio group across 2 directories with 4 updates (#10785) web: bump @lit/localize from 0.12.1 to 0.12.2 in /web (#10786) web: bump @floating-ui/dom from 1.6.8 to 1.6.9 in /web (#10787) web: bump @lit/localize-tools from 0.7.2 to 0.8.0 in /web (#10788) web: bump lit from 3.1.4 to 3.2.0 in /web (#10789) core: bump goauthentik.io/api/v3 from 3.2024062.2 to 3.2024063.1 (#10790) web: bump API Client version (#10779) release: 2024.6.3 website/docs: prepare 2024.6.3 release notes (#10775) website/scripts: updated readme, added docsmg.env file (#10710) web: bump API Client version (#10777) ...
* main: (690 commits) blueprints: handle model referencing non-existent app/model (#10796) website/docs: add more content about flows (#10527) brands: add OIDC webfinger support (#10400) web/admin: fix selectable card colour in dark theme (#10794) web: bump API Client version (#10793) policies: add GeoIP policy (#10454) core: bump debugpy from 1.8.3 to 1.8.5 (#10781) web: bump @sentry/browser from 8.22.0 to 8.23.0 in /web in the sentry group across 1 directory (#10782) website: bump postcss from 8.4.40 to 8.4.41 in /website (#10783) web: bump the wdio group across 2 directories with 4 updates (#10785) web: bump @lit/localize from 0.12.1 to 0.12.2 in /web (#10786) web: bump @floating-ui/dom from 1.6.8 to 1.6.9 in /web (#10787) web: bump @lit/localize-tools from 0.7.2 to 0.8.0 in /web (#10788) web: bump lit from 3.1.4 to 3.2.0 in /web (#10789) core: bump goauthentik.io/api/v3 from 3.2024062.2 to 3.2024063.1 (#10790) web: bump API Client version (#10779) release: 2024.6.3 website/docs: prepare 2024.6.3 release notes (#10775) website/scripts: updated readme, added docsmg.env file (#10710) web: bump API Client version (#10777) ...
* main: (229 commits) blueprints: handle model referencing non-existent app/model (#10796) website/docs: add more content about flows (#10527) brands: add OIDC webfinger support (#10400) web/admin: fix selectable card colour in dark theme (#10794) web: bump API Client version (#10793) policies: add GeoIP policy (#10454) core: bump debugpy from 1.8.3 to 1.8.5 (#10781) web: bump @sentry/browser from 8.22.0 to 8.23.0 in /web in the sentry group across 1 directory (#10782) website: bump postcss from 8.4.40 to 8.4.41 in /website (#10783) web: bump the wdio group across 2 directories with 4 updates (#10785) web: bump @lit/localize from 0.12.1 to 0.12.2 in /web (#10786) web: bump @floating-ui/dom from 1.6.8 to 1.6.9 in /web (#10787) web: bump @lit/localize-tools from 0.7.2 to 0.8.0 in /web (#10788) web: bump lit from 3.1.4 to 3.2.0 in /web (#10789) core: bump goauthentik.io/api/v3 from 3.2024062.2 to 3.2024063.1 (#10790) web: bump API Client version (#10779) release: 2024.6.3 website/docs: prepare 2024.6.3 release notes (#10775) website/scripts: updated readme, added docsmg.env file (#10710) web: bump API Client version (#10777) ...
* main: blueprints: handle model referencing non-existent app/model (#10796) website/docs: add more content about flows (#10527) brands: add OIDC webfinger support (#10400) web/admin: fix selectable card colour in dark theme (#10794) web: bump API Client version (#10793) policies: add GeoIP policy (#10454) core: bump debugpy from 1.8.3 to 1.8.5 (#10781) web: bump @sentry/browser from 8.22.0 to 8.23.0 in /web in the sentry group across 1 directory (#10782) website: bump postcss from 8.4.40 to 8.4.41 in /website (#10783) web: bump the wdio group across 2 directories with 4 updates (#10785) web: bump @lit/localize from 0.12.1 to 0.12.2 in /web (#10786) web: bump @floating-ui/dom from 1.6.8 to 1.6.9 in /web (#10787) web: bump @lit/localize-tools from 0.7.2 to 0.8.0 in /web (#10788) web: bump lit from 3.1.4 to 3.2.0 in /web (#10789) core: bump goauthentik.io/api/v3 from 3.2024062.2 to 3.2024063.1 (#10790) web: bump API Client version (#10779)
Closes #4433
Introduces the new GeoIP policy with the functionality to match client ASN or country (for now).
Opening a draft of this for a high-level overview. There are still many details to iron out likewebwebChecklist
ak test authentik/)make lint-fix)If an API change has been made
make gen-build)If changes to the frontend have been made
make web)If applicable
make website)