Terraform dynamic group creation/loop issues - loops

I've searched and played around quite a bit and I've not come across the solution.
I am trying to manage subscription providers and preview features via the "azurerm_resource_provider_registration" resource.
i've got it working fine if I want to manage just one provider with multiple sub features using the following:
tfvars file
provider_name = "Microsoft.Network"
provider_feature_name = {
feature1 = {
feature_name = "BypassCnameCheckForCustomDomainDeletion"
registered = true
}
feature2 = {
feature_name = "AllowTcpPort25Out"
registered = true
}
}
main.tf
resource "azurerm_resource_provider_registration" "provider_registration" {
name = var.provider_name
dynamic "feature" {
for_each = var.provider_feature_name
content {
name = feature.value.feature_name
registered = feature.value.registered
}
}
}
works great if I only ever want to manage one provider and it's features.
The problem comes when/if I want to add an additional "provider_name". I've tried a separate provider_name block but I keep getting a "unexpected block here" error. if I introduce a block like so;
vars.tf
provider_name = {
provider1 = {
provider_name = "Microsoft.Network" {
feature1 = {
feature_name = "test"
registered = true
}
}
}
provider2 = {
provider_name = "Microsoft.Storage" {
feature2 = {
feature_name = "test2"
registered = true
}
}
}
}
main.tf
resource "azurerm_resource_provider_registration" "provider_registration" {
for_each = var.provider_name
name = each.value.provider_name
dynamic "feature" {
for_each = var.provider_feature_name
content {
name = feature.value.feature_name
registered = feature.value.registered
}
}
I can get it loop but cannot get it to associate only feature1 to provider 1 etc as these features are exclusive to that provider. It associates feature1 to provider 1 & 2.
If I try to introduce a for_each or dynamic group for the "name" value, it comes up with "blocks of type provider not expected here" and/or "argument name is required but no definition was found"
In short, how can I get my main to loop over each provider_name and only associate the sub block of features to that provider (with potential for multiple features per provider type). is it just not possible for this type of resource? or am I just not understanding the loop/for_each documentation correctly.
any help is appreciated
thank you.

First we need to cleanup and optimize the input structure. I have speculated on what the values should be since there are two different hypothetical structures specified in the question, but the structure itself is accurate.
providers = {
"Microsoft.Network" = {
features = { "BypassCnameCheckForCustomDomainDeletion" = true }
}
"Microsoft.Storage" = {
features = { "AllowTcpPort25Out" = true }
}
}
Now we can easily utilize this structure with a for_each meta-argument in the resource.
resource "azurerm_resource_provider_registration" "provider_registration" {
for_each = var.providers
name = each.key
dynamic "feature" {
for_each = each.value.features
content {
name = feature.key
registered = feature.value
}
}
}
and this results in two provider registrations with the corresponding feature mapped to each.

Related

Prevent duplicate field in database

I need to create an api to create an entity which must have a unique name in it's data.
exp:
{
name: "Reza",
...
}
The question is how to throw and error of that name is already taken.
I can handle it by reading the whole table and check if there is no object with that name and then create the entity, but what can I do with concurrency?
if someone else calls the create api in the same time and the same name it's not in db yet, then I can't notice it's a duplicate name, then we encounter a duplication.
The project is on a baas and the service uses a mongodb but i don't access it directly, and because of sharding I can't use unique indexing.
any workaround for this situations?
Try adding the already found names in the db in an array , and compare the name that is being inserted. I will post this code that checks wether the app name that is being inserted to the db if available and render it some where else , you can benefit from the concept.
const updateAppNames = async () => {
let apps = await purchaseFromAppObjectModel.find({}).sort({ name: 1 });
const availableApps = [];
for (var i = 0; i <= apps.length; i++) {
try {
const obj = apps[i];
const appName = obj.appName;
if (availableApps.includes(appName)) {
console.log("This app exists :", appName);
} else {
console.log("This app does not exist :", appName);
availableApps.push(appName);
console.log(availableApps);
}
} catch {
console.error();
}
}
return availableApps;
};
result :
This app does not exist : com.xxx
[ 'com.xxx' ]
This app does not exist : com.xxy
[ 'com.xxx', 'com.xxy' ]
This app exists : com.xxx
This app exists : com.xxx
This app does not exist : any game
[ 'com.xxx', 'com.xxy', 'any game' ]
This app exists : any game
This app exists : any game
This app exists : any game
This app exists : any game
This app does not exist : any.any.any
[
'com.xxx',
'com.xxy',
'any game',
'any.any.any'
]
This app exists : any.any.any
This app exists : any.any.any
This app exists : any.any.any

Uncaught Error in snapshot listener: FirebaseError: Missing or insufficient permissions for chatRooms

I have chatrooms stored in Cloud Firestore that need to be secured and I'm having a difficult time doing so. My state in React looks as such:
export class Messages extends React.Component {
constructor(props) {
super(props);
this.boardID = this.props.settings.id;
this.mainChatRef = database
.collection("boards")
.doc(boardID)
.collection("chats")
.doc("MAIN")
.collection("messages");
this.chatRoomsRef = database
.collection("boards")
.doc(boardID)
.collection("chats");
}
My query in react looks like:
latestMessages = (messageSnapshot) => {
const message = [];
messageSnapshot.forEach((doc) => {
const { text, createdAt, uid } = doc.data();
message.push({
key: doc.id,
text,
createdAt,
uid,
});
});
this.setState({
dataSource: message,
});
}
queryRooms = async () => {
const recentQuery = await this.chatRoomsRef
.where("uidConcat", "in", [
this.props.user.uid + this.state.value.objectID,
this.state.value.objectID + this.props.user.uid,
])
.get();
for (const qSnap of recentQuery.docs) {
const messagesRef = this.chatRoomsRef
.doc(qSnap.id)
.collection("messages")
.orderBy("createdAt")
.limitToLast(30);
messagesRef.onSnapshot(this.latestMessages);
}
}
My database structure:
boards(collection)
{boardId}
chats (collection)
MAIN
{chatId_1}
{chatId_2}
uidArray (has only two UIDs since it's for private chat)
uidConcat: user1_uid+user2_uid
messages(collection)
{message1}
createdAt
text
uid_creator
uidArray (has UID of user1 and user2)
I tried securing my boards as such:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /boards/{boardId} {
allow read, write: if request.time < timestamp.data(2050, 5, 1);
match /chats/MAIN/messages/{messagesId=**} {
allow read, create: if request.auth.uid !=null;
}
match /chats/{chatId} {
allow read, write: if request.auth.uid !=null;
match /messages/{messagesId=**} {
allow read: if (request.auth.uid in get(/databases/{database}/documents/boards/
{boardId}/chats/{chatId}/messages/{messageId}).data.uidArray)
&& request.query.limit <= 30 && request.query.orderBy.createdAt == 'ASC';
allow write: if request.auth.uid != null;
}
}
}
}
}
I wish to have anyone that's authenticated to always have access to the boards MAIN chat and the way I have it written it works. For private rooms I keep getting an error. I've read the documents and I know that Security rules are not filters which is why I added a bunch of AND statements in an attempt to closely resemble my securities to my written code query in React yet I still keep getting Uncaught Errors in snapshot listener: FirebaseError: Missing or insufficient permissions. I am currently writing my rules to secure at the document level within the messages collection but Ideally I'd like to secure at the document level in the chat colection and check if my request.auth.uid is in the document field uidArray as so :
match /chats/{chatId=**} {
allow read, write if request.auth.uid in resource.data.uidArray
}
I imagine securing the documents in the chat collection would be more secure but any method that secures messages is more than appreciated.
The problem is that you're attempting to do a dynamic lookup in your rule based on each row of the data in this line: get(/databases/{database}/documents/boards/{boardId}/chats/{chatId}/messages/{messageId}).data.uidArray)
The critical point to understand here is that rules do not look at the actual data when perform queries. Get operations are a bit different because there's exactly one record to fetch, but for queries, it only examines the query to ensure it cannot fetch data that doesn't match your rule; it never actually loads data to examine. This is covered in docs here and there's a good video overviewing this here. A deeper dive is here.
Role based access is a complex topic and probably not easy to answer in a Stack Overflow given how broad the solutions would be, and how specific they are to your use case. But a naïve alternative would be to list the members in /chats/{chatId} and use a rule like this:
match /chats/{chatId} {
// scoped to chats/{chatId} doc
function isChatMember(uid) {
// assumes this document contains an array of
// uids allowed to read the messages subcollection
return uid in resource.data.members;
}
match /messages/{messagesId=**} {
allow read: if isChatMember(request.auth.uid)
}
}

Keycloak/OIDC : retrieve user groups attributes

I've extracted a user's groups information from the OIDC endpoint of Keycloak, but they don't come with the group ATTRIBUTES I defined (see Attributes tab into the group form, near Settings). Is there a claim to add to my request?
I'm using a RESTeasy client to reach Keycloak's admin API (had much better results than using the provided admin client, yet):
#Path("/admin/realms/{realm}")
public interface KeycloakAdminService {
#GET
#Path("/users/{id}/groups")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
List<GroupRepresentation> getUserGroups(#PathParam("realm") String realm, #PathParam("id") String userId,
#HeaderParam(AUTHORIZATION) String accessToken);
//DEBUG the access token must always be prefixed by "Bearer "
}
So I can fetch a user's groups:
private void fetchUserGroups(UserInfoOIDC infos, String userId) {
log.info("Fetching user groups from {}...", getRealm());
try {
KeycloakAdminService proxy = kcTarget.proxy(KeycloakAdminService.class);
AccessTokenResponse response = authzClient.obtainAccessToken(getAdminUsername(), getAdminPassword());
List<GroupRepresentation> groups = proxy.getUserGroups(getRealm(), userId,
"Bearer " + response.getToken());
infos.importUserGroups(groups); //DEBUG here we go!
} catch (WebApplicationException e) {
log.error("User groups failure on {}: {}", getRealm(), e.getMessage());
}
}
But when it comes to data exploration, it turns out that no attributes are provided into the GroupRepresentation#getAttributes structure.
I've read that claims can be added to user info requests. Does it work on the admin API? How can I achieve that result with RESTeasy templates?
Thx
I was able to achieve this by adding groups/roles info in token other claims property:
For this in keycloak config, go to your client -> mappers & add a group/role mapper. E.g.
Now this info will start coming in your access token:
To access these group attribute in Java you can extract it from otherclaims property of accesstoken. E.g.:
KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext)(request.getAttribute(KeycloakSecurityContext.class.getName()));
AccesToken token = keycloakSecurityContext.getToken();
In below image you can see that otherclaims property of token is filled with groups attribute that we created on keycloak. Note that if we had named "token claim property" as groupXYZ, the otherclaims would be showing:
groupsXYZ=[Administrator]
This is how I could eventually map group attributes (inherited as user attributes, as suspected before) into user informations, into the "other claims" section :
It is possible to inherit attributes from the group by switching on Aggregate attribute values option during the creation of a new User Attribute mapper.
First of all I think the answers above are correct. I was able to achieve what I wanted to do by following recommendations from them.
But I have also broke by production keycloak integration with auth2-proxy which lead to some outage for internal users :)
So, I took time to investigate a bit and came up with creating new client scope and adding custom client role / realm role / group mappers to it.
It works, and also you don't break your working keycloak integrations with other services ;)
Here is all my terraform code which you can you to reproduce what I did:
variable "realm_name" {
type = string
description = "Name of the realm to create"
default = "master"
}
variable "keycloack_user" {
type = string
description = "Keycloak admin user"
default = "admin"
}
variable "keycloack_password" {
type = string
description = "Keycloak admin password"
default = "admin"
}
variable "keycloak_url" {
type = string
description = "Keycloak url"
default = "http://localhost:8090"
}
variable "oauth_fqdn" {
type = string
description = "FQDN of the oauth server used for valid redirects"
default = "http://localhost:3000/*"
}
terraform {
required_version = ">= 1.0.0"
required_providers {
keycloak = {
source = "mrparkers/keycloak"
version = ">= 3.7.0"
}
}
}
provider "keycloak" {
client_id = "admin-cli"
username = var.keycloack_user
password = var.keycloack_password
url = var.keycloak_url
realm = var.realm_name
# base_path = "/auth"
}
data "keycloak_realm" "realm" {
realm = var.realm_name
}
resource "keycloak_openid_client" "client" {
realm_id = data.keycloak_realm.realm.id
client_id = "my-client"
name = "my-client"
enabled = true
access_type = "CONFIDENTIAL"
valid_redirect_uris = [
var.oauth_fqdn
]
login_theme = "keycloak"
standard_flow_enabled = true
}
output "keycloak_client_id" {
value = keycloak_openid_client.client.client_id
}
output "keycloak_client_secret" {
value = keycloak_openid_client.client.client_secret
sensitive = true
}
// creating custom scope
resource "keycloak_openid_client_scope" "this" {
realm_id = data.keycloak_realm.realm.id
name = "group_and_roles"
description = "When requested, this scope will map a user's group memberships and all roles to a claim"
include_in_token_scope = true
}
// creating custom group mapper
resource "keycloak_generic_protocol_mapper" "groups" {
realm_id = data.keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.this.id
name = "groups mapper"
protocol = "openid-connect"
protocol_mapper = "oidc-group-membership-mapper"
config = {
"full.path" : "true",
"id.token.claim" : "true",
"access.token.claim" : "true",
"claim.name" : "groups",
"userinfo.token.claim" : "true"
}
}
// creating custom role mapper for realm level roles
resource "keycloak_generic_protocol_mapper" "realm_roles" {
realm_id = data.keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.this.id
name = "realm roles mapper"
protocol = "openid-connect"
protocol_mapper = "oidc-usermodel-realm-role-mapper"
config = {
"multivalued" : "true",
"userinfo.token.claim" : "true",
"id.token.claim" : "true",
"access.token.claim" : "true",
"claim.name" : "realm_roles",
"jsonType.label" : "String"
}
}
// creating custom role mapper for client level roles
resource "keycloak_generic_protocol_mapper" "client_roles" {
realm_id = data.keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.this.id
name = "client roles mapper"
protocol = "openid-connect"
protocol_mapper = "oidc-usermodel-client-role-mapper"
config = {
"multivalued" : "true",
"userinfo.token.claim" : "true",
"id.token.claim" : "true",
"access.token.claim" : "true",
"claim.name" : "client_roles",
"jsonType.label" : "String"
}
}
// adding custom scope to client as optional
resource "keycloak_openid_client_optional_scopes" "client_optional_scopes" {
realm_id = data.keycloak_realm.realm.id
client_id = keycloak_openid_client.client.id
optional_scopes = [
"address",
"phone",
"offline_access",
"microprofile-jwt",
keycloak_openid_client_scope.this.name
]
}
And later I setup my application level auth config as follows:
AUTH_ISSUER_URL=http://localhost:8090/realms/master
AUTH_CLIENT_ID=my-client
AUTH_CLIENT_SECRET=client-secret-get-it-from-output
AUTH_SCOPES="profile,email,openid,offline_access,group_and_roles"

Not able to view custom data added (used Schema Extension) for a user

In the Azure AD, I have created a Native application and assigned users to it. I want to create an extension property using Microsoft Graph's SchemaExtension. I have written the code for the same and it does not throw any errors and the extension is also added(able to see the Id) through code. I then add the value of the custom property for a user under the application but am not able to view in the Azure portal or in the code when I do the below:
I have supplied the value for the custom property after updating the status of the schema extensions to "Available" but still not able to fetch the custom property value in code or in the UI. Please provide inputs if any.
Below is the code i am using:
var aap = new AzureAuthenticationProvider();
var graphserviceClient = new GraphServiceClient(aap);
IEnumerable<string> targetType = new string[] { "User" };
SchemaExtension extensionDefinition = new SchemaExtension
{
ODataType = "microsoft.graph.ComplexExtensionValue",
Id = $"crmCompanyId",
Description = "This extension is for company id",
Owner = "fac4cdd3-1015-4ed5-8a7b-6008095750e6",
Properties = new List<ExtensionSchemaProperty>()
{
new ExtensionSchemaProperty() { Name = "cid", Type = "String" }
},
Status = "InDevelopment",
TargetTypes = targetType
};
SchemaExtension schemaExtension = //code to add extensionDefinition
var updateSchemaExtension = new SchemaExtension
{
Status = "Available"
};
//update schema to available status
var updatedSchema = code to update extension
IDictionary<string, object> extensionInstance = new Dictionary<string,object>();
extensionInstance.Add(schemaExtension.Id, new
MyDBExtensionClass("testMyExtension"));
User updateUser = new User()
{
AdditionalData = extensionInstance
};
var updatedUser = await graphserviceClient.Users["9ca2bb42-a7f8-487c-
87a0 - 38513057886d"].Request().UpdateAsync(updateUser);//AzureADGraphUser3
- 9ca2bb42 - a7f8 - 487c - 87a0 - 38513057886d
await Task.Delay(10000);
Could you please review the code and let me know what I am missing...I have referred the comments at https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/151 enter code hereby Michael Maier. Let me know in case I need to provide any further info.
Thanks
I was finally able to view the property i had set once i found this link:https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/238
I queried the extension property on a user this way and was able to view the property value i set.

Securing multiple routes with a shared parameter in Nancy Routing

I have a nancy module with multiple routes and want to do a security check on a parameter that is shared by each of the routes. Can I somehow move this parameter 'up in the hierachy' and have the check be a single line?
Example provided below
public SomeModule()
{
//ABC
Get["/prefix/{someSharedParameter}/ABC/{noneSharedParameterA}"] = parameters =>
{
this.RequiresSomethingArbitrary(parameters.someSharedParameter);
...
};
//XYZ
Get["/prefix/{someSharedParameter}/XYZ/{noneSharedParameterX}"] = parameters =>
{
this.RequiresSomethingArbitrary(parameters.someSharedParameter);
...
};
}
So I would like to be able to put the check RequiresSomethingArbitrary on module level here but not sure how or if that is even possible.
EDIT: I had an idea after finding out module base path but it came short.
THIS DOES NOT WORK. Context is null.
public SomeModule(): base("/prefix/{someSharedParameter}")
{
this.RequiresSomethingArbitrary(Context.Parameters.someSharedParameter);
//ABC
Get["/ABC/{noneSharedParameterA}"] = parameters =>
{
...
};
//XYZ
Get["/XYZ/{noneSharedParameterX}"] = parameters =>
{
...
};
}
I also tried only doing the check if context is null because I figured it might be failing on initializing the module only but this will just always skip the check:
public SomeModule(): base("/prefix/{someSharedParameter}")
{
if (Context != null) {
//this is unreachable (?)
this.RequiresSomethingArbitrary(Context.Parameters.someSharedParameter);
}
//ABC
Get["/ABC/{noneSharedParameterA}"] = parameters =>
{
public SomeModule(){
this.RequiresSomethingArbitrary(someSharedParameter);
//route definitions go here
}
All you need to do is move the check to the constructor.

Resources