Skip to content

Empty getUserContext map implementation sets a Pricing-Token in RenewTokenFilter #54

@pgmarc

Description

@pgmarc

Given the following Pricing2Yaml file:

syntaxVersion: 2.1
saasName: PetClinic SaaS
createdAt: "2025-04-21"
version: "1.0.0"
currency: EUR

features:
  hotel:
    description: Acceso al hotel de mascotas
    valueType: BOOLEAN
    defaultValue: false
    expression: planContext['features']['hotel']
    type: DOMAIN

  adoptions:
    description: Acceso al sistema de adopciones
    valueType: BOOLEAN
    defaultValue: false
    expression: planContext['features']['adoptions']
    type: DOMAIN

  onlineConsultation:
    description: Consultas online con veterinarios
    valueType: BOOLEAN
    defaultValue: false
    expression: planContext['features']['onlineConsultation']
    type: DOMAIN

  pets:
    description: Control de mascotas por usuario
    valueType: BOOLEAN
    defaultValue: true
    expression: userContext['pets'] < planContext['usageLimits']['maxPets']
    type: DOMAIN

  visits:
    description: Control de número de visitas por mes
    valueType: BOOLEAN
    defaultValue: true
    expression: userContext['monthlyVisits'] < planContext['usageLimits']['maxVisits']
    type: DOMAIN

usageLimits:
  maxPets:
    description: Número máximo de mascotas por usuario
    valueType: NUMERIC
    defaultValue: 50
    unit: pet
    type: NON_RENEWABLE
    linkedFeatures: [pets]

  maxVisits:
    description: Número máximo de visitas por mes
    valueType: NUMERIC
    defaultValue: 100
    unit: visit
    type: NON_RENEWABLE
    linkedFeatures: [visits]

plans:
  BASIC:
    description: Plan gratuito sin SLA
    price: 0.0
    unit: user/month
    features:
      hotel: { value: false }
      adoptions: { value: false }
      onlineConsultation: { value: false }
    usageLimits:
      maxPets: { value: 50 }
      maxVisits: { value: 100 }

  GOLD:
    description: Plan con soporte extendido y servicios adicionales
    price: 5.0
    unit: user/month
    features:
      hotel: { value: true }
      adoptions: { value: true }
      onlineConsultation: { value: false }
    usageLimits:
      maxPets: { value: 200 }
      maxVisits: { value: 400 }

  PLATINUM:
    description: Plan premium con todas las funcionalidades y SLA completo
    price: 10.0
    unit: user/month
    features:
      hotel: { value: true }
      adoptions: { value: true }
      onlineConsultation: { value: true }
    usageLimits:
      maxPets: { value: 100000 }  # equivalente a "ilimitado"
      maxVisits: { value: 100000 }

And the follwing dummy implementation of PricingContext interface (Notice the empty map of getUserContext):

package org.springframework.samples.petclinic.configuration;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.samples.petclinic.owner.Owner;
import org.springframework.samples.petclinic.user.User;
import org.springframework.samples.petclinic.user.UserService;
import org.springframework.stereotype.Component;

import io.github.isagroup.PricingContext;

@Component
public class PricingConfiguration extends PricingContext {

    @Autowired
    private UserService userService;

    @Value("${petclinic.app.jwtSecret}") private String secret;
    
    @Override 
    public String getJwtSecret() { 
        return secret; 
    }

    @Override
    public String getConfigFilePath() { 
        return "pricing/pricing.yml"; 
    }

    @Override 
    public Map<String, Object> getUserContext() {
        Map<String, Object> userContext = new HashMap<>();
        // Add the logic to retrieve all the data needed within the user context,
        // such as his current usage of the feature XXX.
        return userContext; 
    }
    
    @Override 
    public String getUserPlan() {
        try{
            // Get the current user from the context
            User user = userService.findCurrentUser();
            Owner owner = userService.findOwnerByUser(user.getId());
            return owner.getClinic().getPlan().toString(); 
        } catch (Exception e) {
            // Handle the case where the user is not found or any other exception
            return "BASIC";
        }
    }
}

Doing any call to some endpoint of an API, take as an example Petclinic endpoint

POST http://example.org/api/v1/pets
Content-Type: application/json
Accept: application/json
uthorization: Bearer {{login.response.body.$.token}}
//Pricing-Token: {{login.response.body.$.pricingToken}}

{
    "id": null,
    "name": "Example",
    "birthDate": "2025-03-27",
    "type": {
        "id": 5,
        "name": "bird"
    },
    "owner": {}
}

The filter RenewTokenFilter surprisingly answers with a Pricing-Token:

Pricing-Token: eyJhbGciOiJIUzUxMiJ9.eyJmZWF0dXJlcyI6eyJwZXRzIjp7ImV2YWwiOnRydWUsInVzZWQiOm51bGwsImxpbWl0IjoxMDAwMDB9LCJhZG9wdGlvbnMiOnsiZXZhbCI6dHJ1ZSwidXNlZCI6bnVsbCwibGltaXQiOm51bGx9LCJ2aXNpdHMiOnsiZXZhbCI6dHJ1ZSwidXNlZCI6bnVsbCwibGltaXQiOjEwMDAwMH0sImhvdGVsIjp7ImV2YWwiOnRydWUsInVzZWQiOm51bGwsImxpbWl0IjpudWxsfSwib25saW5lQ29uc3VsdGF0aW9uIjp7ImV2YWwiOnRydWUsInVzZWQiOm51bGwsImxpbWl0IjpudWxsfX0sInN1YiI6IkRlZmF1bHQiLCJleHAiOjE3NDU1MDIwMTIsInVzZXJDb250ZXh0Ijp7fSwiaWF0IjoxNzQ1NDE1NjEyLCJwbGFuQ29udGV4dCI6eyJuYW1lIjoiUExBVElOVU0iLCJkZXNjcmlwdGlvbiI6IlBsYW4gcHJlbWl1bSBjb24gdG9kYXMgbGFzIGZ1bmNpb25hbGlkYWRlcyB5IFNMQSBjb21wbGV0byIsInByaWNlIjoxMC4wLCJ1bml0IjoidXNlci9tb250aCIsImlzUHJpdmF0ZSI6ZmFsc2UsImZlYXR1cmVzIjp7InBldHMiOnRydWUsImFkb3B0aW9ucyI6dHJ1ZSwidmlzaXRzIjp0cnVlLCJob3RlbCI6dHJ1ZSwib25saW5lQ29uc3VsdGF0aW9uIjp0cnVlfSwidXNhZ2VMaW1pdHMiOnsibWF4VmlzaXRzIjoxMDAwMDAsIm1heFBldHMiOjEwMDAwMH19fQ.UOLBaz7CVGbVG5cIv2FsovCQVsY25M54C9C5asP03dPNHHypX43lrPtbzSId0n3K0WUkZHAtRXnKKqxF0QAIFw

Further investigation needs to be done. This is a remainder for mantainers

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions