Γωνιακή - Πώς να ελέγξετε την εξουσιοδότηση βάσει των ρόλων και των οντοτήτων-κρατών

Σήμερα είναι πολύ συνηθισμένο να βρεθείτε στην κατάσταση πιστοποίησης και εξουσιοδότησης κτιρίου στην εφαρμογή σας, σκάβοντας στον ιστό για βιβλιοθήκες και τεχνικές εξουσιοδότησης, είναι πάντα εύκολο να βρείτε λύσεις που προσφέρουν μόνο εξουσιοδότηση βάσης που εμποδίζει μόνο την πρόσβαση σε μια σελίδα, ωστόσο συμβαίνει σχεδόν συχνά ότι χρειάζεστε κάτι άλλο, εξουσιοδότηση οντότητας-κράτους.

Για το TL, DR εδώ είναι το demo και ο κώδικας που χρησιμοποιείται στο demo.

Εικόνες πιστώσεις

Ποιο είναι το heck εξουσιοδότηση από την οντότητα-κράτος;

Λοιπόν, υποθέτω ότι δεν υπήρχε τίποτα καλύτερο στο μυαλό μου για να το ονομάσω γράφοντας αυτή τη θέση. Ωστόσο, περισσότερο ή λιγότερο αυτό που προσπαθώ να πω σχετίζεται με τις καταστάσεις στις οποίες πρέπει να χορηγήσετε ή να μη χορηγήσετε στον τρέχοντα χρήστη τη δυνατότητα να εφαρμόσει συγκεκριμένη ενέργεια εάν η τρέχουσα κατάσταση οντότητα βασίζεται στην κατάσταση Χ και μια διαφορετική ικανότητα όταν η οντότητα η κατάσταση εξαρτάται από την κατάσταση Y και ήταν χειρότερη όταν όλα αυτά πρέπει να βρίσκονται στην ίδια οθόνη ή ακόμα και στην ίδια συνιστώσα. Οποιεσδήποτε βιβλιοθήκες με εξουσιοδότηση βάσης ρόλων θα σας εξοικονομήσουν για αυτό το πρόβλημα.
Κουρασμένος από την ανάγνωση διαφορετικών άρθρων με βάση τη σελίδα βάσης αποτρέπουν λύσεις, άρχισα να σκέφτομαι και να κωδικεύω χωρίς τίποτα στο μυαλό ακόμα, και ξαφνικά ήταν εκεί, βρήκα απλή, απλή και πολύ ευέλικτη λύση για την επίλυση τέτοιων προβλημάτων και αποτελείται από τέσσερα συστατικά.

  • Μια υπηρεσία που παρέχει τις τρέχουσες πληροφορίες χρηστών (αυτό που πραγματικά έχει σημασία είναι ένας τρόπος να παρέχονται οι ρόλοι των χρηστών στους οποίους ανήκει ο τρέχων χρήστης ή του έχει δοθεί να παίξει στην εφαρμογή)
  • Ένας χάρτης άδειας ροής εργασίας (αρχείο JSON)
  • Υπηρεσία παροχής αδειών για να πραγματοποιήσετε τον έλεγχο εξουσιοδότησης
  • Οδηγία για την κατανάλωση του ελέγχου της υπηρεσίας εξουσιοδότησης

Βήμα 1: λάβετε τους τρέχοντες ρόλους χρηστών

Εφαρμόστε μια υπηρεσία για να ανακτήσετε από την πλευρά του διακομιστή (πρώτη φορά) ή από την περίοδο σύνδεσης ή τα cookies όπως προτιμάτε για τις επόμενες χρήσεις του, το σημαντικό είναι εδώ να δώσετε στον κατάλογο των δυνατοτήτων (ρόλων) των χρηστών.

// Παράδειγμα
{
  όνομα: "John Doe",
  ηλεκτρονικό ταχυδρομείο: '[email protected]',
  ρόλοι: ['πωλητής', 'πωλητής_διαχειριστής'], <- αυτό είναι σημαντικό
  accessToken: "Όλοι οι παίκτες εκπροσωπούν τις επιδόσεις τους στα κορίτσια!"
  ... περισσότερα πράγματα
}}

Στην περίπτωσή μου περισσότερο ή λιγότερο αυτό είναι το πώς φαίνεται:

// εισαγωγές εδώ ...
@Injectable ()
κλάση εξαγωγής CurrentUserService {
  ιδιωτικό userSubject = νέο ReplaySubject <Χρήστης> (1);
  private hasUser = false;

  κατασκευαστής (ιδιωτικοί χρήστεςApi: UserApi) {
  }}

  public getUser (): Παρατηρήσεις  {
    αν (! this.hasUser) {
      this.fetchUser ();
    }}
    επιστροφή this.userSubject.asObservable ();
  }}

  public fetchUser (): κενό {
      this.usersApi.getCurrent () // <== http κλήση για να φέρω το userInfo
        .subscribe (χρήστης => {
          // ο χρήστης θα πρέπει να περιέχει ρόλους
          this.hasUser = true;
          this.userSubject.next (χρήστης);
          this.userSubject.com συμπληρώνει ();
        }, (σφάλμα) => {
          this.hasUser = false;
          this.userSubject.error (σφάλμα);
        });
  }}

}}

Δεύτερο βήμα: Δημιουργήστε το χάρτη ροής εργασιών και αδειών

Αυτό δεν είναι παρά η χαρτογράφηση του τι μπορούμε να κάνουμε και ποιος μπορεί να κάνει κάνοντας ένα δέντρο με τις διαφορετικές οντότητες και τις δικές τους πολιτείες. για παράδειγμα, ας φανταστούμε την ακόλουθη διαδικασία πωλήσεων. Η εφαρμογή μας μπορεί να έχει διάφορους τύπους ρόλων. Για παράδειγμα, ας χαρτογραφήσουμε τους ρόλους στον SELLER, τις λύσεις ARCHITECT και CLIENT.

Μην μιλήσετε για τη διαδικασία:

  • Πρώτα ο πωλητής τοποθετεί μια ευκαιρία πώλησης εκτελώντας την ενέργεια Προσθέστε νέα ευκαιρία έτσι πιθανώς δημιουργείται η κατάσταση ευκαιρίας
  • Σε αυτό το σημείο, ο ΠΕΛΑΤΗΣ & ΠΩΛΗΤΗΣ μπορεί να προσθέσει τις απαιτήσεις στην ευκαιρία, ώστε και οι δύο να μπορούν να εφαρμόσουν τη δράση Προσθέστε τις απαιτήσεις όταν οι απαιτήσεις τοποθετούνται τότε η κατάσταση της ευκαιρίας μπορεί να αλλάξει σε υποβληθεί
  • Μόλις τοποθετηθούν οι απαιτήσεις, ο ΑΡΧΙΤΕΚΤΗΣ μπορεί να θέλει να προσθέσει μια λύση, ώστε να χρειαστεί μια ενέργεια: Η μεταφόρτωση της λύσης και ίσως η κατάσταση μπορεί να αλλάξει
  • Μόλις παρέχεται η λύση, ο ΠΕΛΑΤΗΣ μπορεί να θέλει να αποδεχθεί, ώστε να χρειαστεί μια ενέργεια για την έγκριση της λύσης και η κατάσταση θα αλλάξει σε solution_approved

Πρόκειται να περικόψουμε τη διαδικασία εδώ, διαφορετικά θα αυξηθεί πάρα πολύ και δεν συμβαίνει αυτό το διάβασμα. Έτσι με βάση αυτή τη διαδικασία η χαρτογράφηση και υποθέτοντας ότι η οντότητα ευκαιρίας έχει ένα πεδίο που παρακολουθεί την κατάσταση, η δουλειά μας θα μοιάζει με αυτό:

{
  "ευκαιρία": {
    "addOpportunity": {"allowedRoles": ["ΠΩΛΗΤΗΣ"]}},
    "δημιουργήθηκε": {
      "addRequirement": {"allowedRoles": ["ΠΩΛΗΤΗΣ", "ΠΕΛΑΤΗΣ"]]
    },
    "υποβλήθηκε": {
      "addSolution": {"allowedRoles": ["ARCHITECT"])
    },
    "λυθεί": {
      "approveSolution": {"allowedRoles": ["CLIENT"])
    }}
}}

Βήμα 3: Η υπηρεσία εξουσιοδότησης ελέγχου για να καταναλώσει τον χάρτη worflow & permissions

Τώρα που έχουμε τη διαδικασία χαρτογραφηθεί σε ένα χάρτη ροής εργασιών και αδειών, πρέπει να δημιουργήσουμε μια υπηρεσία για να την καταναλώσουμε και να ελέγξουμε αν ο χρήστης είναι εξουσιοδοτημένος ή όχι και θα μπορούσε να μοιάζει με αυτόν:

// δηλώσεις εισαγωγής εδώ
// από τον τρόπο στο γωνιακό-cli μπορούμε να βάλουμε το αρχείο JSON στο
// enviroment.ts
@Injectable ()
κλάση εξαγωγής WorkflowEvents {
  ιδιωτική ανάγνωση WORKFLOW_EVENTS = περιβάλλον ['ροή εργασίας'];
  ιδιωτικός χρήστηςRoles: Ορισμός ;
  // θυμηθείτε το βήμα 1; χρησιμοποιείται εδώ
  κατασκευαστής (ιδιωτικό currentUserService: CurrentUserService) {
  }}
  // επιστρέφει ένα boolean παρατηρήσιμο
  δημόσιο checkAuthorization (διαδρομή: any): Obsible  {
    // φορτώνουμε τους ρόλους μόνο μία φορά
   αν (! thisuserRoles) {
      επιστροφή this.currentUserService.getUser ()
        .map (currentUser => currentUser.gr)
        .do (ρόλοι => {
          const ρόλοι = roles.map (ρόλο => όνομα_ρυθμίας);
          this.userRoles = νέο σετ (ρόλοι);
        })
        .map (ρόλοι => this.doCheckAuthorization (διαδρομή));
    }}
    επιστρέφει Observ.of (this.doCheckAuthorization (διαδρομή));
  }}

  ιδιωτική εντολή doCheckAuthorization (διαδρομή: συμβολοσειρά []): boolean {
    αν (path.length) {
      const entry = this.findEntry (αυτό.WORKFLOW_EVENTS, διαδρομή);
      αν (entry && entry ['allowedRoles']
             && this.userRoles.size) {
        επιστροφή εισόδου
        .some (επιτρεπόμενηRole => this.userRoles.has (επιτρεπόμενηRole));
      }}
      επιστροφή ψευδής?
    }}
    επιστροφή ψευδής?
  }}
/ **
 * Αναδρομικά βρείτε την καταχώρηση χάρτη ροής εργασίας με βάση τις συμβολοσειρές διαδρομής
 * /
ιδιωτικό findEntry (currentObject: οποιοδήποτε, κλειδιά: συμβολοσειρά [], index = 0) {
    κλειδί const = κλειδιά [ευρετήριο];
    αν (currentObject [κλειδί] & & index 

Βασικά, αυτό που κάνει είναι να εξετάσει μια έγκυρη καταχώρηση και να ελέγξει αν οι τρέχοντες ρόλοι χρηστών περιλαμβάνονται στα επιτρεπόμενα Roles.

Αρχείο 4: Η οδηγία

Μόλις έχουμε τους τρέχοντες ρόλους των χρηστών, ένα δέντρο αδειών ροής εργασίας και μια υπηρεσία για να ελέγξουμε την εξουσιοδότηση για τους τρέχοντες ρόλους των χρηστών, τώρα χρειαζόμαστε έναν τρόπο να το βάλουμε ζωντανό και ο καλύτερος τρόπος στο γωνιακό 2/4 είναι μια οδηγία. Αρχικά η οδηγία που έγραψα ήταν μια οδηγία χαρακτηριστικού που περιστρέφεται με το χαρακτηριστικό γνώρισμα CSS εμφάνισης, αλλά αυτό θα μπορούσε να οδηγήσει σε προβλήματα επιδόσεων επειδή τα παρατεταμένα συστατικά εξακολουθούν να φορτώνονται στο DOM, οπότε είναι καλύτερο να χρησιμοποιούμε διαρθρωτικές οδηγίες (Χάρη στον συνάδελφό μου Πέττιο Χολάκοφ , δείτε τη διαφορά εδώ), επειδή μπορούμε να τροποποιήσουμε το DOM του στοιχείου στόχου και είναι αποβιώσαντες έτσι ώστε να αποφύγουμε τη φόρτωση των αχρησιμοποίητων στοιχείων.

@Διευθυντικός({
  επιλογέας: '[appCanAccess]'
})
εξαγωγή class CanAccessDirective υλοποιεί OnInit, OnDestroy {
  @Input ('appCanAccess') appCanAccess: συμβολοσειρά σειρά[];
  ιδιωτική άδεια $: Εγγραφή;

  κατασκευαστής (ιδιωτικό templateRef: TemplateRef ,
              ιδιωτικό viewContainer: ViewContainerRef,
              ιδιωτική ροή εργασίαςEvents: WorkflowEvents) {
  }}

  ngOnInit (): κενό {
    this.applyPermission ();
  }}

  ιδιωτική applyPermission (): void {
    this.permission $ = this.workflowEvents
                        .checkAuthorization (αυτό.appCanAccess)
      .subscribe (εξουσιοδοτημένος => {
        εάν (εξουσιοδοτημένος) {
          this.viewContainer.createEmbeddedView (this.templateRef);
        } else {
          this.viewContainer.clear ();
        }}
      });
  }}

  ngOnDestroy (): void {
    this.permission $ .unsubscribe ();
  }}

}}

Τελικά η εργασία που προέκυψε

Τώρα αυτό που έχουμε όλα όσα χρειαζόμαστε είναι χρόνος να τα θέσουμε σε ενέργεια. έτσι στο πρότυπο HTML, το μόνο πράγμα που πρέπει να κάνουμε είναι κάτι σαν τον ακόλουθο κώδικα

Ας υποθέσουμε ότι έχουμε ένα στοιχείο δείγματος που περιλαμβάνει το αντικείμενο ευκαιρίας:

@Συστατικό({
  επιλογέας: 'sp-pricing-panel',
  πρότυπο: `
<συστατικό ίδιου δείγματος>
    <κουμπί * appCanAccess = "['ευκαιρία', 'addOpportunity']">
      Προσθήκη ευκαιρίας
    
 
 
<έγκριση-λύση-συστατικό * appCanAccess = "['ευκαιρία', opportunityObject.state, 'approveSolution']"> 
 "
})
εξαγωγή κλάσηςSampleComponent υλοποιεί OnInit {

  @Input () opportunityObject: οποιοδήποτε?
  κατασκευαστής () {
  }}

  ngOnInit () {
  }}
}}

Και αυτό είναι, μπορούμε να έχουμε ένα απλό συστατικό που παρουσιάζει συμπεριφορά ανάλογα με τους ρόλους των χρηστών και την κατάσταση της οντότητας.

Χάρη στους συναδέλφους μου Petyo και Govind για τα αλιεύματα και τους κριτικούς για την κακή μου κωδικοποίηση μπορούμε να βρούμε αυτή τη λύση που μπορεί να λειτουργεί τέλεια στις ανάγκες μας, ελπίζω ότι αυτό θα σας βοηθήσει επίσης.

ΙΟΥΝ 2018, εργασία μικρού δείγματος => https://emsedano.github.io/ng-entity-state/