Trying to create a public CKShare to a private database - core-data

I am trying to create a CKShare to the private database but with a public permission, meaning the user only needs to have the url and doesn't need to be specifically invited. The code I use to create the share is as follows but I always end up with a private share.
private func createShare(_ school: School) async
{
do
{
let (_, share, _) = try await CoreDataStack.shared.persistentContainer.share([school], to: nil)
share[CKShare.SystemFieldKey.title] = school.name
share.publicPermission = CKShare.ParticipantPermission.readWrite
self.share = share
} catch
{
error.log("ContentView")
print("Failed to create share")
}
}
Any suggestions?

This isn't the full answer but it solves my problem. I forgot or failed to notice that on the Share controller there is a Share Options link that allows me to set the share to "Anyone with the link". Ideally what I'd like would be for that to be the default, but as I say, this solves the problem for now.

Related

Fetching OpenIdConnectConfiguration while offline/no connection to AuthServer

I've been working on how to save OpenIdConnecConfiguration locally in the odd case that the AuthServer is not reachable but the frontend client (e.g. Phone) still has a valid refresh token which still needs to be validated again when signing in. It is also needed to be saved locally to a file in the case that the backend (e.g. WCF) has restarted due to a update or the frequent restarts it has (once a day)
What I've done so far, I've saved the JSON object of the ".well-known/openid-configuration" to a file/variable and now I want to create the OpenIdConnectConfiguration object.
OpenIdConnectConfiguration.Create(json) does a lot of the work but the signingKeys do not get created. I think maybe it's because the authorization endpoint needs to be created in some other manner maybe?
Or maybe I'm doing this the wrong way and there is another solution to this issue. I'm working in C#.
Edit: I know there are some caveats to what I'm doing. I need to check once in awhile to see if the public key has been changed, but security wise it should be fine to save the configuration because it's already public. I only need the public key to validate/sign the jwt I get from the user and nothing more.
Figured out a solution after looking through OpenIdConnectConfiguration.cs on the official github.
When fetching the OpenIdConnectConfiguration the first time, use Write() to get a JSON string and use it to save it to file.
Afterwards when loading the file, use Create() to create the OpenIdConnectConfiguration again from the JSON string (This had the issue of not saving the signingKeys as said in the question, but alas! there is a fix)
Lastly to fix the issue with the signingKeys not being created, (this is what I found out from the github class) all we need to do is loop through the JsonWebKeySet and create them as is done in the class. We already have all the information needed from the initial load and therefore only need to create them again.
I'll leave the code example below of what I did. I still need to handle checking if he key has been changed/expired which is the next step I'll be tackling.
interface IValidationPersistence
{
void SaveOpenIdConnectConfiguration(OpenIdConnectConfiguration openIdConfig);
OpenIdConnectConfiguration LoadOpenIdConnectionConfiguration();
}
class ValidationPersistence : IValidationPersistence
{
private readonly string _windowsTempPath = Path.GetTempPath();
private readonly string _fileName = "TestFileName";
private readonly string _fullFilePath;
public ValidationPersistence()
{
_fullFilePath = _windowsTempPath + _fileName;
}
public OpenIdConnectConfiguration LoadOpenIdConnectionConfiguration()
{
FileService fileService = new FileService();
OpenIdConnectConfiguration openIdConfig = OpenIdConnectConfiguration.Create(fileService.LoadFromJSONFile<string>(_fullFilePath));
foreach (SecurityKey key in openIdConfig.JsonWebKeySet.GetSigningKeys())
{
openIdConfig.SigningKeys.Add(key);
}
return openIdConfig;
}
public void SaveOpenIdConnectConfiguration(OpenIdConnectConfiguration openIdConfig)
{
FileService fileService = new FileService();
fileService.WriteToJSONFile(OpenIdConnectConfiguration.Write(openIdConfig), _fullFilePath);
}
}

EasyAdmin 3 Only List data belonging to logged in user

I just recently migrated from easyadmin 2 to easyadmin 3 and in my lists i can now see all the data from the entitiy (e.g. Company).
In easyadmin 2 it automatically (at least that´s what i guess) limited the output belonging to the logged-in user.
I have read that i can set custom functions for each Controller like
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
{
if (!in_array("ROLE_ADMIN",$this->getUser()->getRoles())) {
$qb = $this->get(EntityRepository::class)->createQueryBuilder($searchDto, $entityDto, $fields, $filters);
$qb->andWhere('entity.creator = :user');
$qb->setParameter('user', $this->getUser());
return $qb;
}
}
but that can´t be the solution i guess since somehow it worked in easyadmin 2 as well without having to write custom indexQueryBuilder functions.
Any help very much appreciated.
I have roles ADMIN, MANAGER, COURIER, so my code:
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto;
use EasyCorp\Bundle\EasyAdminBundle\Orm\EntityRepository;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection;
use Doctrine\ORM\QueryBuilder;
class LuggageManagerCrudController extends AbstractCrudController
{
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
{
$qb = $this->get(EntityRepository::class)->createQueryBuilder($searchDto, $entityDto, $fields, $filters);
if (in_array('ROLE_MANAGER', $this->getUser()->getRoles())) {
$qb->andWhere('entity.manager = :user');
} else {
$qb->andWhere('entity.courier = :user');
}
$qb->setParameter('user', $this->getUser()->getEmail());
return $qb;
}
}
And I have another AdminCrudController for ADMIN's roles

How to access Shopware\Core\System\SalesChannel\SalesChannelContext in Entity Related event subscribers in shopware 6?

I have built a custom subscriber in my plugin for Shopware 6 that subscribes to
\Shopware\Core\Content\Product\ProductEvents::PRODUCT_WRITTEN_EVENT = 'product.written';
public function onProductWrittenEntity(EntityWrittenEvent $event): void
{
//$event->getContext() is returning the Shopware\Core\Framework\Context
}
I want to get domain URL of this current salesChannel having those productIds which are currently written. how can i do that?
You can obtain the salesChannelId by running the following code:
$event->getContext()->getSource()->getSalesChannelId()
With that salesChannelId and inserting the SalesChannelRepository via the services.xml into your Subscriber, you can load the required information from that sales-channel.
When you edit the products over the API or inside the administration, you are in a "admin context", that means no sales-channel is available. This is because your changes are globally and you are not limited to a specific sales-channel.
The SalesChannelContext is only available if the action that was triggered originated in the storefront or came over the store-api.
Long story short:
You can't access the salesChannelContext from the EntityWrittenEvent, as most of the times there is no specific SalesChannel, where the event was triggered.
Maybe you can explain your use case a little bit more, so we can suggest alternatives.
in case someone run in this Problem:
You can for example Subscribe to the Event "SalesChannelContextResolvedEvent". Store all the Data in a variable as type array (Argument2 from the construct, "$this->saleschannelContext"). And call it in where ever you need it, for example an other event (you can call it so -> "$this->salechannelContext").
public function __construct(EntityRepository $discountExtensionRepository){
$this->discountExtensionRepository = $discountExtensionRepository;
$this->salechannelContext = array();
}
public static function getSubscribedEvents(): array{
return [
SalesChannelContextResolvedEvent::class => "onPageLoaded",
ProductEvents::PRODUCT_LOADED_EVENT => 'onProductsLoaded'
];
}
public function onPageLoaded(SalesChannelContextResolvedEvent $event){
$this->salechannelContext = $event->getSaleschannelContext();
}
public function onProductsLoaded(EntityLoadedEvent $event):void{
dump($this->salechannelContext);
}
Probably not the best practice way because i guess there is a way to get it directly from die Product event, but it is one way of manys.
[EDIT]: You can get all Storefront and Product informations with this event.
use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntityLoadedEvent;
public static function getSubscribedEvents(): array{
return [
'sales_channel.product.loaded' => 'onSalesChannelLoaded'
];
}
public function onSalesChannelLoaded(SalesChannelEntityLoadedEvent $event):void{

How can I get more debugging info about the Symfony2 security system?

In general, how can I get useful debugging output about the decisions made by the various components of the Symfony2 security system during request processing? I would love to see things like what firewall and access_control statements were applied and why. What tools are there to make it easier to address the perennial "Why did I get redirected to the login form again" mystery?
You can use Blackfire if you need detailed debug information.
If its not sufficient then you can use WebProfilerBundle it has good debugging information.
If that also not work for you then you can create your own Data Collector Services.
Data Collectors are just like profiler extensions and they can help you to collect different data like routes, debug information or mailer data also. You can customize them according to your need.
Please check the documentation Here
Please check SecurityDebugBundle This will answer your all questions.
Use it carefully, as it requires different permissions.
By Reading its code you will understand how Data Collectors can help you out in debugging.
Hope that will help you.
Here is the DataCollecotr from SecurityDebugBundle:
class FirewallCollector
{
const HAS_RESPONSE = SecurityDebugDataCollector::DENIED;
private $securityContext;
private $container;
public function __construct(
SecurityContextInterface $securityContext,
Container $container
) {
$this->securityContext = $securityContext;
//Container dependency is a bad thing. This is to be refactored to a compiler pass
//where all the firewall providers will be fetched
$this->container = $container;
}
public function collect(Request $request, \Exception $exception)
{
$token = $this->securityContext->getToken();
if (!method_exists($token, 'getProviderKey')) {
return;
}
$providerKey = $token->getProviderKey();
$map = $this->container->get('security.firewall.map.context.' . $providerKey);
$firewallContext = $map->getContext();
$event = new GetResponseEvent(
new SimpleHttpKernel(),
$request,
HttpKernelInterface::MASTER_REQUEST
);
$firewalls = array();
foreach ($firewallContext[0] as $i => $listener) {
$firewalls[$i]= array('class' => get_class($listener), 'result' => SecurityDebugDataCollector::GRANTED);
try {
$listener->handle($event);
} catch (AccessDeniedException $ade) {
$firewalls[$i]['result'] = SecurityDebugDataCollector::DENIED;
break;
}
if ($event->hasResponse()) {
$firewalls[$i]['result'] = self::HAS_RESPONSE;
break;
}
}
return $firewalls;
}
}
This gives alot information on Firewall.
This Bundle Also contains SecurityDebugDataCollector and VotersCollector. So it can give information on all security components.

Where to check user email does not already exist?

I have an account object that creates a user like so;
public class Account
{
public ICollection<User> Users { get; set; }
public User CreateUser(string email)
{
User user = new User(email);
user.Account = this;
Users.Add(user);
}
}
In my service layer when creating a new user I call this method. However there is a rule that the users email MUST be unique to the account, so where does this go? To me it should go in the CreateUser method with an extra line that just checks that the email is unique to the account.
However if it were to do this then ALL the users for the account would need to be loaded in and that seems like a bit of an overhead to me. It would be better to query the database for the users email - but doing that in the method would require a repository in the account object wouldn't it? Maybe the answer then is when loading the account from the repository instead of doing;
var accountRepository.Get(12);
//instead do
var accountRepository.GetWithUserLoadedOnEmail(12, "someone#example.com");
Then the account object could still check the Users collection for the email and it would have been eagerly loaded in if found.
Does this work? What would you do?
I'm using NHibernate as an ORM.
First off, I do not think you should use exceptions to handle "normal" business logic like checking for duplicate email addresses. This is a well document anti-pattern and is best avoided. Keep the constraint on the DB and handle any duplicate exceptions because they cannot be avoid, but try to keep them to a minimum by checking. I would not recommend locking the table.
Secondly, you've put the DDD tag on this questions, so I'll answer it in a DDD way. It looks to me like you need a domain service or factory. Once you have moved this code in a domain service or factory, you can then inject a UserRepository into it and make a call to it to see if a user already exists with that email address.
Something like this:
public class CreateUserService
{
private readonly IUserRepository userRepository;
public CreateUserService(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
public bool CreateUser(Account account, string emailAddress)
{
// Check if there is already a user with this email address
User userWithSameEmailAddress = userRepository.GetUserByEmailAddress(emailAddress);
if (userWithSameEmailAddress != null)
{
return false;
}
// Create the new user, depending on you aggregates this could be a factory method on Account
User newUser = new User(emailAddress);
account.AddUser(newUser);
return true;
}
}
This allows you to separate the responsiblities a little and use the domain service to coordinate things. Hope that helps!
If you have properly specified the constraints on the users table, the add should throw an exception telling you that there is already a duplicate value. You can either catch that exception in the CreateUser method and return null or some duplicate user status code, or let it flow out and catch it later.
You don't want to test if it exists in your code and then add, because there is a slight possibility that between the test and the add, someone will come along and add the same email with would cause the exception to be thrown anyway...
public User CreateUser(string email)
{
try
{
User user = new User(email);
user.Account = this;
user.Insert();
catch (SqlException e)
{
// It would be best to check for the exception code from your db...
return null;
}
}
Given that "the rule that the users email MUST be unique to the account", then the most important thing is to specify in the database schema that the email is unique, so that the database INSERT will fail if the email is duplicate.
You probably can't prevent two users adding the same email nearly-simultaneously, so the next thing is that the code should handle (gracefully) an INSERT failure cause by the above.
After you've implemented the above, seeing whether the email is unique before you do the insert is just optional.

Resources